Merge branch 'develop' of github.com:thecodingmachine/workadventure

This commit is contained in:
_Bastler 2021-11-16 10:56:33 +01:00
commit e43e1d8463
49 changed files with 755 additions and 489 deletions

View File

@ -19,9 +19,13 @@ ACME_EMAIL=
MAX_PER_GROUP=4 MAX_PER_GROUP=4
MAX_USERNAME_LENGTH=8 MAX_USERNAME_LENGTH=8
OIDC_CLIENT_ID= OPID_CLIENT_ID=
OIDC_CLIENT_SECRET= OPID_CLIENT_SECRET=
OIDC_CLIENT_ISSUER= 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 # 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= CONTACT_URL=

View File

@ -51,9 +51,9 @@ services:
JITSI_URL: ${JITSI_URL} JITSI_URL: ${JITSI_URL}
JITSI_ISS: ${JITSI_ISS} JITSI_ISS: ${JITSI_ISS}
FRONT_URL : ${FRONT_URL} FRONT_URL : ${FRONT_URL}
OIDC_CLIENT_ID: ${OIDC_CLIENT_ID} OPID_CLIENT_ID: ${OPID_CLIENT_ID}
OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET} OPID_CLIENT_SECRET: ${OPID_CLIENT_SECRET}
OIDC_CLIENT_ISSUER: ${OIDC_CLIENT_ISSUER} OPID_CLIENT_ISSUER: ${OPID_CLIENT_ISSUER}
labels: labels:
- "traefik.http.middlewares.strip-pusher-prefix.stripprefix.prefixes=/pusher" - "traefik.http.middlewares.strip-pusher-prefix.stripprefix.prefixes=/pusher"
- "traefik.http.routers.pusher.rule=PathPrefix(`/pusher`)" - "traefik.http.routers.pusher.rule=PathPrefix(`/pusher`)"

View File

@ -62,6 +62,7 @@
} + (if adminUrl != null then { } + (if adminUrl != null then {
"ADMIN_API_URL": adminUrl, "ADMIN_API_URL": adminUrl,
"ADMIN_API_TOKEN": env.ADMIN_API_TOKEN, "ADMIN_API_TOKEN": env.ADMIN_API_TOKEN,
"ADMIN_SOCKETS_TOKEN": env.ADMIN_SOCKETS_TOKEN,
} else {}) } else {})
}, },
"front": { "front": {

View File

@ -40,6 +40,7 @@ services:
TURN_USER: "" TURN_USER: ""
TURN_PASSWORD: "" TURN_PASSWORD: ""
START_ROOM_URL: "$START_ROOM_URL" START_ROOM_URL: "$START_ROOM_URL"
DISABLE_ANONYMOUS: "$DISABLE_ANONYMOUS"
command: yarn run start command: yarn run start
volumes: volumes:
- ./front:/usr/src/app - ./front:/usr/src/app
@ -67,9 +68,12 @@ services:
JITSI_URL: $JITSI_URL JITSI_URL: $JITSI_URL
JITSI_ISS: $JITSI_ISS JITSI_ISS: $JITSI_ISS
FRONT_URL: http://localhost FRONT_URL: http://localhost
OIDC_CLIENT_ID: $OIDC_CLIENT_ID OPID_CLIENT_ID: $OPID_CLIENT_ID
OIDC_CLIENT_SECRET: $OIDC_CLIENT_SECRET OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET
OIDC_CLIENT_ISSUER: $OIDC_CLIENT_ISSUER 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: volumes:
- ./pusher:/usr/src/app - ./pusher:/usr/src/app
labels: labels:

View File

@ -43,6 +43,8 @@ services:
START_ROOM_URL: "$START_ROOM_URL" START_ROOM_URL: "$START_ROOM_URL"
MAX_PER_GROUP: "$MAX_PER_GROUP" MAX_PER_GROUP: "$MAX_PER_GROUP"
MAX_USERNAME_LENGTH: "$MAX_USERNAME_LENGTH" MAX_USERNAME_LENGTH: "$MAX_USERNAME_LENGTH"
DISABLE_ANONYMOUS: "$DISABLE_ANONYMOUS"
OPID_LOGIN_SCREEN_PROVIDER: "$OPID_LOGIN_SCREEN_PROVIDER"
command: yarn run start command: yarn run start
volumes: volumes:
- ./front:/usr/src/app - ./front:/usr/src/app
@ -68,9 +70,12 @@ services:
JITSI_URL: $JITSI_URL JITSI_URL: $JITSI_URL
JITSI_ISS: $JITSI_ISS JITSI_ISS: $JITSI_ISS
FRONT_URL: http://play.workadventure.localhost FRONT_URL: http://play.workadventure.localhost
OIDC_CLIENT_ID: $OIDC_CLIENT_ID OPID_CLIENT_ID: $OPID_CLIENT_ID
OIDC_CLIENT_SECRET: $OIDC_CLIENT_SECRET OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET
OIDC_CLIENT_ISSUER: $OIDC_CLIENT_ISSUER 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: volumes:
- ./pusher:/usr/src/app - ./pusher:/usr/src/app
labels: labels:

View File

@ -15,7 +15,7 @@ When controls are disabled, the user cannot move anymore using keyboard input. T
Example: Example:
```javascript ```javascript
WA.room.onEnterZone('myZone', () => { WA.room.onEnterLayer('myZone').subscribe(() => {
WA.controls.disablePlayerControls(); WA.controls.disablePlayerControls();
WA.ui.openPopup("popupRectangle", 'This is an imporant message!', [{ WA.ui.openPopup("popupRectangle", 'This is an imporant message!', [{
label: "Got it!", label: "Got it!",
@ -25,5 +25,5 @@ WA.room.onEnterZone('myZone', () => {
popup.close(); popup.close();
} }
}]); }]);
}); })
``` ```

View File

@ -49,7 +49,7 @@ Example:
let helloWorldPopup; let helloWorldPopup;
// Open the popup when we enter a given zone // 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!', [{ WA.ui.openPopup("popupRectangle", 'Hello world!', [{
label: "Close", label: "Close",
className: "primary", className: "primary",
@ -57,13 +57,13 @@ helloWorldPopup = WA.room.onEnterZone('myZone', () => {
// Close the popup when the "Close" button is pressed. // Close the popup when the "Close" button is pressed.
popup.close(); popup.close();
} }
}); }]);
}]); });
// Close the popup when we leave the zone. // Close the popup when we leave the zone.
WA.room.onLeaveZone('myZone', () => { WA.room.onLeaveLayer("myZone").subscribe(() => {
helloWorldPopup.close(); helloWorldPopup.close();
}); })
``` ```
### Add custom menu ### Add custom menu

View File

@ -273,7 +273,7 @@ class IframeListener {
registerScript(scriptUrl: string): Promise<void> { registerScript(scriptUrl: string): Promise<void> {
return new Promise<void>((resolve, reject) => { 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") { if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") {
// Using external iframe mode ( // Using external iframe mode (

View File

@ -1,6 +1,6 @@
<script lang="typescript"> <script lang="typescript">
import { requestedScreenSharingState, screenSharingAvailableStore } from "../Stores/ScreenSharingStore"; import {requestedScreenSharingState, screenSharingAvailableStore} from "../Stores/ScreenSharingStore";
import { isSilentStore, requestedCameraState, requestedMicrophoneState } from "../Stores/MediaStore"; import {isSilentStore, requestedCameraState, requestedMicrophoneState} from "../Stores/MediaStore";
import monitorImg from "./images/monitor.svg"; import monitorImg from "./images/monitor.svg";
import monitorCloseImg from "./images/monitor-close.svg"; import monitorCloseImg from "./images/monitor-close.svg";
import cinemaImg from "./images/cinema.svg"; import cinemaImg from "./images/cinema.svg";
@ -9,10 +9,10 @@
import microphoneCloseImg from "./images/microphone-close.svg"; import microphoneCloseImg from "./images/microphone-close.svg";
import layoutPresentationImg from "./images/layout-presentation.svg"; import layoutPresentationImg from "./images/layout-presentation.svg";
import layoutChatImg from "./images/layout-chat.svg"; import layoutChatImg from "./images/layout-chat.svg";
import { layoutModeStore } from "../Stores/StreamableCollectionStore"; import {layoutModeStore} from "../Stores/StreamableCollectionStore";
import { LayoutMode } from "../WebRtc/LayoutManager"; import {LayoutMode} from "../WebRtc/LayoutManager";
import { peerStore } from "../Stores/PeerStore"; import {peerStore} from "../Stores/PeerStore";
import { onDestroy } from "svelte"; import {onDestroy} from "svelte";
function screenSharingClick(): void { function screenSharingClick(): void {
if (isSilent) return; if (isSilent) return;
@ -60,9 +60,9 @@
<div class="btn-cam-action"> <div class="btn-cam-action">
<div class="btn-layout nes-btn is-dark" on:click={switchLayoutMode} class:hide={$peerStore.size===0}> <div class="btn-layout nes-btn is-dark" on:click={switchLayoutMode} class:hide={$peerStore.size===0}>
{#if $layoutModeStore === LayoutMode.Presentation } {#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} {:else}
<img src={layoutChatImg} style="padding: 2px" alt="Switch to presentation mode"> <img src={layoutChatImg} style="padding: 2px" alt="Switch to presentation mode">
{/if} {/if}
</div> </div>
<div class="btn-micro nes-btn is-dark" on:click={microphoneClick} class:disabled={!$requestedMicrophoneState || <div class="btn-micro nes-btn is-dark" on:click={microphoneClick} class:disabled={!$requestedMicrophoneState ||
@ -77,9 +77,9 @@
<div class="btn-video nes-btn is-dark" on:click={cameraClick} class:disabled={!$requestedCameraState || <div class="btn-video nes-btn is-dark" on:click={cameraClick} class:disabled={!$requestedCameraState ||
isSilent}> isSilent}>
{#if $requestedCameraState && !isSilent} {#if $requestedCameraState && !isSilent}
<img src={cinemaImg} alt="Turn on webcam"> <img src={cinemaImg} alt="Turn on webcam">
{:else} {:else}
<img src={cinemaCloseImg} alt="Turn off webcam"> <img src={cinemaCloseImg} alt="Turn off webcam">
{/if} {/if}
</div> </div>
<div class="btn-monitor nes-btn is-dark" on:click={screenSharingClick} class:hide={!$screenSharingAvailableStore <div class="btn-monitor nes-btn is-dark" on:click={screenSharingClick} class:hide={!$screenSharingAvailableStore
@ -91,4 +91,4 @@
{/if} {/if}
</div> </div>
</div> </div>
</div> </div>

View File

@ -39,7 +39,7 @@
}) })
function onKeyDown(e: KeyboardEvent) { function onKeyDown(e:KeyboardEvent) {
if (e.key === 'Escape') { if (e.key === 'Escape') {
emoteMenuStore.closeEmoteMenu(); emoteMenuStore.closeEmoteMenu();
} }
@ -55,7 +55,7 @@
</script> </script>
<svelte:window on:keydown={onKeyDown} /> <svelte:window on:keydown={onKeyDown}/>
<div class="emote-menu-container"> <div class="emote-menu-container">
<div class="emote-menu" bind:this={emojiContainer}></div> <div class="emote-menu" bind:this={emojiContainer}></div>
@ -73,4 +73,4 @@
.emote-menu { .emote-menu {
pointer-events: all; pointer-events: all;
} }
</style> </style>

View File

@ -1,10 +1,60 @@
<script lang="ts"> <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"; import {contactPageStore} from "../../Stores/MenuStore";
</script> </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"> <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 { iframe {
border: none; border: none;
height: calc(100% - 56px); height: calc(100% - 56px);

View File

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

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

View File

@ -3,11 +3,11 @@
import SettingsSubMenu from "./SettingsSubMenu.svelte"; import SettingsSubMenu from "./SettingsSubMenu.svelte";
import ProfileSubMenu from "./ProfileSubMenu.svelte"; import ProfileSubMenu from "./ProfileSubMenu.svelte";
import WorldsSubMenu from "./WorldsSubMenu.svelte"; import WorldsSubMenu from "./WorldsSubMenu.svelte";
import CreateMapSubMenu from "./CreateMapSubMenu.svelte";
import AboutRoomSubMenu from "./AboutRoomSubMenu.svelte"; import AboutRoomSubMenu from "./AboutRoomSubMenu.svelte";
import GlobalMessageSubMenu from "./GlobalMessagesSubMenu.svelte"; import GlobalMessageSubMenu from "./GlobalMessagesSubMenu.svelte";
import ContactSubMenu from "./ContactSubMenu.svelte"; import ContactSubMenu from "./ContactSubMenu.svelte";
import CustomSubMenu from "./CustomSubMenu.svelte" import CustomSubMenu from "./CustomSubMenu.svelte"
import GuestSubMenu from "./GuestSubMenu.svelte";
import { import {
checkSubMenuToShow, checkSubMenuToShow,
customMenuIframe, customMenuIframe,
@ -20,21 +20,21 @@
import type {Unsubscriber} from "svelte/store"; import type {Unsubscriber} from "svelte/store";
import {sendMenuClickedEvent} from "../../Api/iframe/Ui/MenuItem"; import {sendMenuClickedEvent} from "../../Api/iframe/Ui/MenuItem";
let activeSubMenu: string = SubMenusInterface.settings; let activeSubMenu: string = SubMenusInterface.profile;
let activeComponent: typeof SettingsSubMenu | typeof CustomSubMenu = SettingsSubMenu; let activeComponent: typeof ProfileSubMenu | typeof CustomSubMenu = ProfileSubMenu;
let props: { url: string, allowApi: boolean }; let props: { url: string, allowApi: boolean };
let unsubscriberSubMenuStore: Unsubscriber; let unsubscriberSubMenuStore: Unsubscriber;
onMount(() => { onMount(() => {
unsubscriberSubMenuStore = subMenusStore.subscribe(() => { unsubscriberSubMenuStore = subMenusStore.subscribe(() => {
if(!get(subMenusStore).includes(activeSubMenu)) { if(!get(subMenusStore).includes(activeSubMenu)) {
switchMenu(SubMenusInterface.settings); switchMenu(SubMenusInterface.profile);
} }
}) })
checkSubMenuToShow(); checkSubMenuToShow();
switchMenu(SubMenusInterface.settings); switchMenu(SubMenusInterface.profile);
}) })
onDestroy(() => { onDestroy(() => {
@ -56,8 +56,8 @@
case SubMenusInterface.worlds: case SubMenusInterface.worlds:
activeComponent = WorldsSubMenu; activeComponent = WorldsSubMenu;
break; break;
case SubMenusInterface.createMap: case SubMenusInterface.invite:
activeComponent = CreateMapSubMenu; activeComponent = GuestSubMenu;
break; break;
case SubMenusInterface.aboutRoom: case SubMenusInterface.aboutRoom:
activeComponent = AboutRoomSubMenu; activeComponent = AboutRoomSubMenu;

View File

@ -30,7 +30,6 @@
z-index: 90; z-index: 90;
position: relative; position: relative;
margin: 25px; margin: 25px;
img { img {
pointer-events: auto; pointer-events: auto;
width: 24px; width: 24px;
@ -38,8 +37,7 @@
margin: 3px margin: 3px
} }
} }
.menuIcon img:hover{
.menuIcon img:hover {
transform: scale(1.2); transform: scale(1.2);
} }
@ -53,4 +51,4 @@
} }
} }
} }
</style> </style>

View File

@ -1,104 +1,168 @@
<script lang="typescript"> <script lang="typescript">
import { gameManager } from "../../Phaser/Game/GameManager"; import {gameManager} from "../../Phaser/Game/GameManager";
import { SelectCompanionScene, SelectCompanionSceneName } from "../../Phaser/Login/SelectCompanionScene"; import {SelectCompanionScene, SelectCompanionSceneName} from "../../Phaser/Login/SelectCompanionScene";
import { menuIconVisiblilityStore, menuVisiblilityStore, userIsConnected } from "../../Stores/MenuStore"; import {menuIconVisiblilityStore, menuVisiblilityStore, userIsConnected} from "../../Stores/MenuStore";
import { selectCompanionSceneVisibleStore } from "../../Stores/SelectCompanionStore"; import {selectCompanionSceneVisibleStore} from "../../Stores/SelectCompanionStore";
import { loginSceneVisibleStore } from "../../Stores/LoginSceneStore"; import {LoginScene, LoginSceneName} from "../../Phaser/Login/LoginScene";
import { selectCharacterSceneVisibleStore } from "../../Stores/SelectCharacterStore"; import {loginSceneVisibleStore} from "../../Stores/LoginSceneStore";
import { SelectCharacterScene, SelectCharacterSceneName } from "../../Phaser/Login/SelectCharacterScene"; import {selectCharacterSceneVisibleStore} from "../../Stores/SelectCharacterStore";
import { connectionManager } from "../../Connexion/ConnectionManager"; import {SelectCharacterScene, SelectCharacterSceneName} from "../../Phaser/Login/SelectCharacterScene";
import { PROFILE_URL } from "../../Enum/EnvironmentVariable"; import {connectionManager} from "../../Connexion/ConnectionManager";
import { localUserStore } from "../../Connexion/LocalUserStore"; import {PROFILE_URL} from "../../Enum/EnvironmentVariable";
import { EnableCameraScene, EnableCameraSceneName } from "../../Phaser/Login/EnableCameraScene"; import {localUserStore} from "../../Connexion/LocalUserStore";
import { enableCameraSceneVisibilityStore } from "../../Stores/MediaStore"; 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); menuVisiblilityStore.set(false);
menuIconVisiblilityStore.set(false); menuIconVisiblilityStore.set(false);
} }
function openEditCompanionScene() { function openEditCompanionScene(){
disableMenuStores(); disableMenuStores();
selectCompanionSceneVisibleStore.set(true); 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(); disableMenuStores();
selectCharacterSceneVisibleStore.set(true); selectCharacterSceneVisibleStore.set(true);
gameManager.leaveGame(SelectCharacterSceneName, new SelectCharacterScene()); gameManager.leaveGame(SelectCharacterSceneName,new SelectCharacterScene());
} }
function logOut() { function logOut(){
disableMenuStores(); disableMenuStores();
loginSceneVisibleStore.set(true); loginSceneVisibleStore.set(true);
connectionManager.logout(); connectionManager.logout();
} }
function getProfileUrl() { function getProfileUrl(){
return PROFILE_URL + `?token=${localUserStore.getAuthToken()}`; return PROFILE_URL + `?token=${localUserStore.getAuthToken()}`;
} }
function openEnableCameraScene() { function openEnableCameraScene(){
disableMenuStores(); disableMenuStores();
enableCameraSceneVisibilityStore.showEnableCameraScene(); enableCameraSceneVisibilityStore.showEnableCameraScene();
gameManager.leaveGame(EnableCameraSceneName, new EnableCameraScene()); gameManager.leaveGame(EnableCameraSceneName,new EnableCameraScene());
} }
</script> </script>
<div class="customize-main"> <div class="customize-main">
<section> <div class="submenu">
<button type="button" class="nes-btn" on:click|preventDefault={openEditSkinScene}>Edit Skin</button> <section>
<button type="button" class="nes-btn" on:click|preventDefault={openEditCompanionScene}>Edit Companion</button> <!--
</section> <button type="button" class="nes-btn" on:click|preventDefault={openEditNameScene}>
<section> <img src={btnProfileSubMenuIdentity} alt="Edit your name">
<button type="button" class="nes-btn" on:click|preventDefault={openEnableCameraScene}>Setup camera</button> <span class="btn-hover">Edit your name</span>
</section> </button>
{#if $userIsConnected} -->
<section> <button type="button" class="nes-btn" on:click|preventDefault={openEditSkinScene}>
{#if PROFILE_URL != undefined} <img src={btnProfileSubMenuWoka} alt="Edit your Avatar">
<iframe title="profile" src="{getProfileUrl()}"></iframe> <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}
<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">Sign in</a>
</section>
{/if} {/if}
</section> </div>
<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">Sign in</a>
</section>
{/if}
</div> </div>
<style lang="scss"> <style lang="scss">
div.customize-main { div.customize-main{
overflow-y: auto; width: 100%;
height: 90%; display: inline-flex;
section { div.submenu{
display: flex; height: 100%;
justify-content: center; width: 50px;
align-items: center;
flex-wrap: wrap;
margin-bottom: 20px;
iframe { button {
width: 100%; transition: all .5s ease;
height: 50vh; text-align: left;
border: none; white-space: nowrap;
} margin-bottom: 10px;
max-height: 44px;
button {
height: 50px; img {
width: 250px; 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;
align-items: center;
flex-wrap: wrap;
margin-bottom: 20px;
iframe {
width: 100%;
height: 50vh;
border: none;
}
button {
height: 50px;
width: 250px;
}
}
}
} }
@media only screen and (max-width: 800px) { @media only screen and (max-width: 800px) {
div.customize-main section button { div.customize-main.content section button {
width: 130px; width: 130px;
} }
} }
</style> </style>

View File

@ -1,11 +1,11 @@
<script lang="typescript"> <script lang="typescript">
import { obtainedMediaConstraintStore } from "../Stores/MediaStore"; import {obtainedMediaConstraintStore} from "../Stores/MediaStore";
import { localStreamStore, isSilentStore } from "../Stores/MediaStore"; import {localStreamStore, isSilentStore} from "../Stores/MediaStore";
import SoundMeterWidget from "./SoundMeterWidget.svelte"; import SoundMeterWidget from "./SoundMeterWidget.svelte";
import { onDestroy } from "svelte"; import {onDestroy} from "svelte";
import { srcObject } from "./Video/utils"; import {srcObject} from "./Video/utils";
let stream: MediaStream | null; let stream : MediaStream|null;
const unsubscribe = localStreamStore.subscribe(value => { const unsubscribe = localStreamStore.subscribe(value => {
if (value.type === 'success') { if (value.type === 'success') {
@ -30,7 +30,7 @@
<div> <div>
<div class="video-container nes-container is-rounded is-dark div-myCamVideo" <div class="video-container nes-container is-rounded is-dark div-myCamVideo"
class:hide={!$obtainedMediaConstraintStore.video || isSilent}> class:hide={!$obtainedMediaConstraintStore.video || isSilent}>
{#if $localStreamStore.type === "success" && $localStreamStore.stream} {#if $localStreamStore.type === "success" && $localStreamStore.stream}
<video class="myCamVideo" use:srcObject={stream} autoplay muted playsinline></video> <video class="myCamVideo" use:srcObject={stream} autoplay muted playsinline></video>
<SoundMeterWidget stream={stream}></SoundMeterWidget> <SoundMeterWidget stream={stream}></SoundMeterWidget>
@ -39,4 +39,4 @@
<div class="is-silent" class:hide={isSilent}> <div class="is-silent" class:hide={isSilent}>
Silent zone Silent zone
</div> </div>
</div> </div>

View 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

View 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_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

View 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_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

View 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

View File

@ -42,20 +42,11 @@ class ConnectionManager {
const nonce = localUserStore.generateNonce(); const nonce = localUserStore.generateNonce();
localUserStore.setAuthToken(null); localUserStore.setAuthToken(null);
//TODO fix me to redirect this URL by pusher if (!this._currentRoom || !this._currentRoom.iframeAuthentication) {
if (!this._currentRoom) {
loginSceneVisibleIframeStore.set(false); loginSceneVisibleIframeStore.set(false);
return null; return null;
} }
const redirectUrl = new URL(`${this._currentRoom.iframeAuthentication}`);
// 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);
}
redirectUrl.searchParams.append("state", state); redirectUrl.searchParams.append("state", state);
redirectUrl.searchParams.append("nonce", nonce); redirectUrl.searchParams.append("nonce", nonce);
redirectUrl.searchParams.append("playUri", this._currentRoom.key); redirectUrl.searchParams.append("playUri", this._currentRoom.key);
@ -88,6 +79,16 @@ class ConnectionManager {
const connexionType = urlManager.getGameConnexionType(); const connexionType = urlManager.getGameConnexionType();
this.connexionType = connexionType; this.connexionType = connexionType;
this._currentRoom = null; 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) { if (connexionType === GameConnexionTypes.login) {
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl())); this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
if (this.loadOpenIDScreen() !== null) { if (this.loadOpenIDScreen() !== null) {
@ -96,15 +97,19 @@ class ConnectionManager {
urlManager.pushRoomIdToUrl(this._currentRoom); urlManager.pushRoomIdToUrl(this._currentRoom);
} else if (connexionType === GameConnexionTypes.jwt) { } else if (connexionType === GameConnexionTypes.jwt) {
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get("code");
const state = urlParams.get("state"); if (!token) {
if (!state || !localUserStore.verifyState(state)) { const code = urlParams.get("code");
throw "Could not validate state!"; const state = urlParams.get("state");
if (!state || !localUserStore.verifyState(state)) {
throw "Could not validate state!";
}
if (!code) {
throw "No Auth code provided";
}
localUserStore.setCode(code);
} }
if (!code) {
throw "No Auth code provided";
}
localUserStore.setCode(code);
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl())); this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
try { try {
await this.checkAuthUserConnexion(); await this.checkAuthUserConnexion();
@ -132,11 +137,11 @@ class ConnectionManager {
this._currentRoom = await Room.createRoom( this._currentRoom = await Room.createRoom(
new URL( new URL(
window.location.protocol + window.location.protocol +
"//" + "//" +
window.location.host + window.location.host +
roomUrl + roomUrl +
window.location.search + window.location.search +
window.location.hash window.location.hash
) )
); );
urlManager.pushRoomIdToUrl(this._currentRoom); urlManager.pushRoomIdToUrl(this._currentRoom);
@ -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 //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)); 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. //todo: add here some kind of warning if authToken has expired.
if (!this.authToken && !this._currentRoom.authenticationMandatory) { if (!this.authToken && !this._currentRoom.authenticationMandatory) {
await this.anonymousLogin(); await this.anonymousLogin();
} else { } else {
try { try {
await this.checkAuthUserConnexion(); await this.checkAuthUserConnexion();
analyticsClient.loggedWithSso();
} catch (err) { } catch (err) {
console.error(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 this.localUser = localUserStore.getLocalUser() as LocalUser; //if authToken exist in localStorage then localUser cannot be null
@ -208,23 +219,20 @@ class ConnectionManager {
analyticsClient.identifyUser(this.localUser.uuid, this.localUser.email); 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(); this.serviceWorker = new _ServiceWorker();
return Promise.resolve(this._currentRoom); return Promise.resolve(this._currentRoom);
} }
public async anonymousLogin(isBenchmark: boolean = false): Promise<void> { public async anonymousLogin(isBenchmark: boolean = false): Promise<void> {
localUserStore.setAuthToken(null); const data = await Axios.post(`${PUSHER_URL}/anonymLogin`).then((res) => res.data);
try { this.localUser = new LocalUser(data.userUuid, []);
const data = await Axios.post(`${PUSHER_URL}/anonymLogin`).then((res) => res.data); this.authToken = data.authToken;
this.localUser = new LocalUser(data.userUuid, []); if (!isBenchmark) {
this.authToken = data.authToken; // In benchmark, we don't have a local storage.
if (!isBenchmark) { localUserStore.saveUser(this.localUser);
// In benchmark, we don't have a local storage. localUserStore.setAuthToken(this.authToken);
localUserStore.saveUser(this.localUser);
localUserStore.setAuthToken(this.authToken);
}
} catch (error) {
this.loadOpenIDScreen();
} }
} }
@ -261,9 +269,9 @@ class ConnectionManager {
reject( reject(
new Error( new Error(
"An error occurred while connecting to socket server. Retrying. Code: " + "An error occurred while connecting to socket server. Retrying. Code: " +
event.code + event.code +
", Reason: " + ", Reason: " +
event.reason event.reason
) )
); );
}); });
@ -293,33 +301,30 @@ class ConnectionManager {
//set connected store for menu at false //set connected store for menu at false
userIsConnected.set(false); userIsConnected.set(false);
const token = localUserStore.getAuthToken();
const state = localUserStore.getState(); const state = localUserStore.getState();
const code = localUserStore.getCode(); 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 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, []); if (!token) {
this.authToken = authToken; if (!state || !localUserStore.verifyState(state)) {
throw "Could not validate state!";
}
if (!code) {
throw "No Auth code provided";
}
}
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.saveUser(this.localUser);
localUserStore.setAuthToken(this.authToken); this.authToken = authToken;
gameManager.setPlayerName(username); gameManager.setPlayerName(username);
//user connected, set connected store for menu at true //user connected, set connected store for menu at true
userIsConnected.set(true); userIsConnected.set(true);
if (this.connexionType === GameConnexionTypes.anonymous) {
this.connexionType = GameConnexionTypes.empty
}
} }
async getWorlds() { async getWorlds() {

View File

@ -120,19 +120,12 @@ class LocalUserStore {
return localStorage.getItem(fullscreenKey) === "true"; return localStorage.getItem(fullscreenKey) === "true";
} }
setLastRoomUrl(roomUrl: string | null): void { setLastRoomUrl(roomUrl: string): void {
if (roomUrl) { localStorage.setItem(lastRoomUrl, roomUrl.toString());
localStorage.setItem(lastRoomUrl, roomUrl.toString()); caches.open(cacheAPIIndex).then((cache) => {
caches.open(cacheAPIIndex).then((cache) => { const stringResponse = new Response(JSON.stringify({ roomUrl }));
const stringResponse = new Response(JSON.stringify({ roomUrl })); cache.put(`/${lastRoomUrl}`, stringResponse);
cache.put(`/${lastRoomUrl}`, stringResponse); });
});
} else {
localStorage.removeItem(lastRoomUrl);
caches.open(cacheAPIIndex).then((cache) => {
cache.delete(`/${lastRoomUrl}`);
});
}
} }
getLastRoomUrl(): string { getLastRoomUrl(): string {
return ( return (
@ -172,8 +165,15 @@ class LocalUserStore {
verifyState(value: string): boolean { verifyState(value: string): boolean {
const oldValue = localStorage.getItem(state); const oldValue = localStorage.getItem(state);
if (!oldValue) {
localStorage.setItem(state, value);
return true;
}
return oldValue === value; return oldValue === value;
} }
setState(value: string) {
localStorage.setItem(state, value);
}
getState(): string | null { getState(): string | null {
return localStorage.getItem(state); return localStorage.getItem(state);
} }

View File

@ -1,5 +1,5 @@
import Axios from "axios"; 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 type { CharacterTexture } from "./LocalUser";
import { localUserStore } from "./LocalUserStore"; import { localUserStore } from "./LocalUserStore";
@ -14,8 +14,8 @@ export interface RoomRedirect {
export class Room { export class Room {
public readonly id: string; public readonly id: string;
public readonly isPublic: boolean; public readonly isPublic: boolean;
private _authenticationMandatory: boolean = false; private _authenticationMandatory: boolean = DISABLE_ANONYMOUS as boolean;
private _iframeAuthentication?: string; private _iframeAuthentication?: string = OPID_LOGIN_SCREEN_PROVIDER;
private _mapUrl: string | undefined; private _mapUrl: string | undefined;
private _textures: CharacterTexture[] | undefined; private _textures: CharacterTexture[] | undefined;
private instance: string | undefined; private instance: string | undefined;
@ -106,8 +106,8 @@ export class Room {
this._mapUrl = data.mapUrl; this._mapUrl = data.mapUrl;
this._textures = data.textures; this._textures = data.textures;
this._group = data.group; this._group = data.group;
this._authenticationMandatory = data.authenticationMandatory || false; this._authenticationMandatory = data.authenticationMandatory || (DISABLE_ANONYMOUS as boolean);
this._iframeAuthentication = data.iframeAuthentication; this._iframeAuthentication = data.iframeAuthentication || OPID_LOGIN_SCREEN_PROVIDER;
this._contactPage = data.contactPage || CONTACT_URL; this._contactPage = data.contactPage || CONTACT_URL;
return new MapDetail(data.mapUrl, data.textures); return new MapDetail(data.mapUrl, data.textures);
} }

View File

@ -23,6 +23,8 @@ export const CONTACT_URL = process.env.CONTACT_URL || undefined;
export const PROFILE_URL = process.env.PROFILE_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_API_KEY: string = (process.env.POSTHOG_API_KEY as string) || "";
export const POSTHOG_URL = process.env.POSTHOG_URL || undefined; 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; export const isMobile = (): boolean => window.innerWidth <= 800 || window.innerHeight <= 600;

View File

@ -66,7 +66,7 @@ export abstract class Character extends Container {
this.playAnimation(direction, moving); this.playAnimation(direction, moving);
}) })
.catch(() => { .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.addTextures(textures, frame);
this.invisible = false; this.invisible = false;
this.playAnimation(direction, moving); this.playAnimation(direction, moving);
@ -77,7 +77,7 @@ export abstract class Character extends Container {
fontFamily: '"Press Start 2P"', fontFamily: '"Press Start 2P"',
fontSize: "8px", fontSize: "8px",
strokeThickness: 2, strokeThickness: 2,
stroke: "#000", stroke: "gray",
}); });
this.playerName.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX); this.playerName.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
this.add(this.playerName); this.add(this.playerName);
@ -155,56 +155,56 @@ export abstract class Character extends Container {
{ {
key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Walk}`, key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Walk}`,
frameModel: name, frameModel: name,
frames: [ 0, 1, 2, 1 ], frames: [0, 1, 2, 1],
frameRate: 10, frameRate: 10,
repeat: -1, repeat: -1,
}, },
{ {
key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Walk}`, key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Walk}`,
frameModel: name, frameModel: name,
frames: [ 3, 4, 5, 4 ], frames: [3, 4, 5, 4],
frameRate: 10, frameRate: 10,
repeat: -1, repeat: -1,
}, },
{ {
key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Walk}`, key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Walk}`,
frameModel: name, frameModel: name,
frames: [ 6, 7, 8, 7 ], frames: [6, 7, 8, 7],
frameRate: 10, frameRate: 10,
repeat: -1, repeat: -1,
}, },
{ {
key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Walk}`, key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Walk}`,
frameModel: name, frameModel: name,
frames: [ 9, 10, 11, 10 ], frames: [9, 10, 11, 10],
frameRate: 10, frameRate: 10,
repeat: -1, repeat: -1,
}, },
{ {
key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Idle}`, key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Idle}`,
frameModel: name, frameModel: name,
frames: [ 1 ], frames: [1],
frameRate: 10, frameRate: 10,
repeat: 1, repeat: 1,
}, },
{ {
key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Idle}`, key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Idle}`,
frameModel: name, frameModel: name,
frames: [ 4 ], frames: [4],
frameRate: 10, frameRate: 10,
repeat: 1, repeat: 1,
}, },
{ {
key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Idle}`, key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Idle}`,
frameModel: name, frameModel: name,
frames: [ 7 ], frames: [7],
frameRate: 10, frameRate: 10,
repeat: 1, repeat: 1,
}, },
{ {
key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Idle}`, key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Idle}`,
frameModel: name, frameModel: name,
frames: [ 10 ], frames: [10],
frameRate: 10, frameRate: 10,
repeat: 1, repeat: 1,
}, },
@ -213,7 +213,7 @@ export abstract class Character extends Container {
protected playAnimation(direction: PlayerAnimationDirections, moving: boolean): void { protected playAnimation(direction: PlayerAnimationDirections, moving: boolean): void {
if (this.invisible) return; if (this.invisible) return;
for (const [ texture, sprite ] of this.sprites.entries()) { for (const [texture, sprite] of this.sprites.entries()) {
if (!sprite.anims) { if (!sprite.anims) {
console.error("ANIMS IS NOT DEFINED!!!"); console.error("ANIMS IS NOT DEFINED!!!");
return; return;

View File

@ -1,7 +1,7 @@
//The list of all the player textures, both the default models and the partial textures used for customization //The list of all the player textures, both the default models and the partial textures used for customization
export interface BodyResourceDescriptionListInterface { export interface BodyResourceDescriptionListInterface {
[ key: string ]: BodyResourceDescriptionInterface; [key: string]: BodyResourceDescriptionInterface;
} }
export interface BodyResourceDescriptionInterface { export interface BodyResourceDescriptionInterface {
@ -434,10 +434,7 @@ export const ACCESSORIES_RESOURCES: BodyResourceDescriptionListInterface = {
name: "accessory_mate_bottle", name: "accessory_mate_bottle",
img: "resources/customisation/character_accessories/mate_bottle1.png", img: "resources/customisation/character_accessories/mate_bottle1.png",
}, },
accessory_mask: { accessory_mask: { name: "accessory_mask", img: "resources/customisation/character_accessories/mask.png" },
name: "accessory_mask",
img: "resources/customisation/character_accessories/mask.png",
},
wheelchair: { wheelchair: {
name: "accessory_wheelchair", name: "accessory_wheelchair",
img: "resources/customisation/character_accessories/wheelchair.png", img: "resources/customisation/character_accessories/wheelchair.png",

View File

@ -57,7 +57,7 @@ export class GameMapPropertiesListener {
} }
}); });
// Open a new co-website by the property. // Open a new co-website by the property.
this.gameMap.onEnterLayer((newLayers) => { this.gameMap.onEnterLayer((newLayers) => {
const handler = () => { const handler = () => {
newLayers.forEach(layer => { newLayers.forEach(layer => {
@ -109,6 +109,11 @@ export class GameMapPropertiesListener {
return; return;
} }
this.coWebsitesOpenByLayer.set(layer, {
coWebsite: undefined,
state: OpenCoWebsiteState.LOADING,
});
const openWebsiteFunction = () => { const openWebsiteFunction = () => {
coWebsiteManager.loadCoWebsite( coWebsiteManager.loadCoWebsite(
openWebsiteProperty as string, openWebsiteProperty as string,

View File

@ -89,8 +89,6 @@ import { get } from "svelte/store";
import { contactPageStore } from "../../Stores/MenuStore"; import { contactPageStore } from "../../Stores/MenuStore";
import { GameMapProperties } from "./GameMapProperties"; import { GameMapProperties } from "./GameMapProperties";
export interface GameSceneInitInterface { export interface GameSceneInitInterface {
initPosition: PointInterface | null; initPosition: PointInterface | null;
reconnecting: boolean; 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) // 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 // 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 url = new URL(file.src);
const host = url.host.split(":")[ 0 ]; const host = url.host.split(":")[0];
if ( if (
window.location.protocol === "https:" && window.location.protocol === "https:" &&
file.src === this.MapUrlFile && file.src === this.MapUrlFile &&
@ -326,8 +324,8 @@ export class GameScene extends DirtyScene {
//eslint-disable-next-line @typescript-eslint/no-explicit-any //eslint-disable-next-line @typescript-eslint/no-explicit-any
(this.load as any).rexWebFont({ (this.load as any).rexWebFont({
custom: { custom: {
families: [ "Press Start 2P" ], families: ["Press Start 2P"],
urls: [ "/resources/fonts/fonts.css" ], urls: ["/resources/fonts/fonts.css"],
testString: "abcdefg", 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. // 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; let itemFactory: ItemFactoryInterface;
@ -404,7 +402,7 @@ export class GameScene extends DirtyScene {
// TODO: we should pass here a factory to create sprites (maybe?) // TODO: we should pass here a factory to create sprites (maybe?)
// Do we have a state for this object? // 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); const actionableItem = itemFactory.factory(this, object, state);
this.actionableItems.set(actionableItem.getId(), actionableItem); 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(); this.scene.wake();
}); });
} }
@ -722,8 +720,8 @@ export class GameScene extends DirtyScene {
if (item === undefined) { if (item === undefined) {
console.warn( console.warn(
'Received an event about object "' + 'Received an event about object "' +
message.itemId + message.itemId +
'" but cannot find this item on the map.' '" but cannot find this item on the map.'
); );
return; return;
} }
@ -931,8 +929,8 @@ export class GameScene extends DirtyScene {
} else { } else {
console.error( console.error(
"Error while opening a popup. Cannot find an object on the map with name '" + "Error while opening a popup. Cannot find an object on the map with name '" +
openPopupEvent.targetObject + openPopupEvent.targetObject +
"'. The first parameter of WA.openPopup() must be the name of a rectangle object in your map." "'. The first parameter of WA.openPopup() must be the name of a rectangle object in your map."
); );
return; return;
} }
@ -943,7 +941,8 @@ export class GameScene extends DirtyScene {
html += `<input id="popupinput-${openPopupEvent.popupId}" class="nes-input" />` html += `<input id="popupinput-${openPopupEvent.popupId}" class="nes-input" />`
} }
html += `</div>`; html += `</div>`;
const buttonContainer = `<div class="buttonContainer"</div>`;
const buttonContainer = '<div class="buttonContainer"</div>';
html += buttonContainer; html += buttonContainer;
let id = 0; let id = 0;
for (const button of openPopupEvent.buttons) { for (const button of openPopupEvent.buttons) {
@ -1190,7 +1189,7 @@ export class GameScene extends DirtyScene {
const jsonTilesetDir = eventTileset.url.substr(0, eventTileset.url.lastIndexOf("/")); 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 //Initialise the firstgid to 1 because if there is no tileset in the tilemap, the firstgid will be 1
let newFirstgid = 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 (lastTileset) {
//If there is at least one tileset in the tilemap then calculate the firstgid of the new tileset //If there is at least one tileset in the tilemap then calculate the firstgid of the new tileset
newFirstgid = lastTileset.firstgid + lastTileset.tilecount; newFirstgid = lastTileset.firstgid + lastTileset.tilecount;
@ -1290,14 +1289,14 @@ export class GameScene extends DirtyScene {
if (phaserLayers === []) { if (phaserLayers === []) {
console.warn( console.warn(
'Could not find layer with name that contains "' + 'Could not find layer with name that contains "' +
layerName + layerName +
'" when calling WA.hideLayer / WA.showLayer' '" when calling WA.hideLayer / WA.showLayer'
); );
return; return;
} }
for (let i = 0; i < phaserLayers.length; i++) { for (let i = 0; i < phaserLayers.length; i++) {
phaserLayers[ i ].setVisible(visible); phaserLayers[i].setVisible(visible);
phaserLayers[ i ].setCollisionByProperty({ collides: true }, visible); phaserLayers[i].setCollisionByProperty({ collides: true }, visible);
} }
} }
this.markDirty(); this.markDirty();

View File

@ -17,7 +17,7 @@ export class LoginScene extends ResizableScene {
this.name = gameManager.getPlayerName() || ""; this.name = gameManager.getPlayerName() || "";
} }
preload() { } preload() {}
create() { create() {
loginSceneVisibleIframeStore.set(false); loginSceneVisibleIframeStore.set(false);
@ -28,9 +28,9 @@ export class LoginScene extends ResizableScene {
gameManager.currentStartedRoom.authenticationMandatory gameManager.currentStartedRoom.authenticationMandatory
) { ) {
connectionManager.loadOpenIDScreen(); connectionManager.loadOpenIDScreen();
} else { loginSceneVisibleIframeStore.set(true);
loginSceneVisibleStore.set(true);
} }
loginSceneVisibleStore.set(true);
} }
public login(name: string): void { public login(name: string): void {
@ -43,7 +43,7 @@ export class LoginScene extends ResizableScene {
loginSceneVisibleStore.set(false); loginSceneVisibleStore.set(false);
} }
update(time: number, delta: number): void { } update(time: number, delta: number): void {}
public onResize(): void { } public onResize(): void {}
} }

View File

@ -67,6 +67,9 @@ function createChatMessagesStore() {
}); });
}, },
addPersonnalMessage(text: string) { addPersonnalMessage(text: string) {
//post message iframe listener
iframeListener.sendUserInputChat(text);
newChatMessageStore.set(text); newChatMessageStore.set(text);
update((list) => { update((list) => {
const lastMessage = list[list.length - 1]; const lastMessage = list[list.length - 1];

View File

@ -35,21 +35,21 @@ export enum SubMenusInterface {
settings = "Settings", settings = "Settings",
profile = "Profile", profile = "Profile",
worlds = "Worlds", worlds = "Worlds",
createMap = "Create a Map", invite = "Invite",
aboutRoom = "About the Room", aboutRoom = "Credit",
globalMessages = "Global Messages", globalMessages = "Global Messages",
contact = "Contact", contact = "Contact",
} }
function createSubMenusStore() { function createSubMenusStore() {
const { subscribe, update } = writable<string[]>([ const { subscribe, update } = writable<string[]>([
SubMenusInterface.settings,
SubMenusInterface.profile, SubMenusInterface.profile,
SubMenusInterface.worlds, SubMenusInterface.worlds,
SubMenusInterface.createMap,
SubMenusInterface.aboutRoom,
SubMenusInterface.globalMessages, SubMenusInterface.globalMessages,
SubMenusInterface.contact, SubMenusInterface.contact,
SubMenusInterface.settings,
SubMenusInterface.invite,
SubMenusInterface.aboutRoom,
]); ]);
return { return {

View File

@ -517,6 +517,7 @@ class CoWebsiteManager {
}; };
this.coWebsites.push(coWebsite); this.coWebsites.push(coWebsite);
// this.cowebsiteSubIconsDom.appendChild(icon);
const onTimeoutPromise = new Promise<void>((resolve) => { const onTimeoutPromise = new Promise<void>((resolve) => {
setTimeout(() => resolve(), 2000); setTimeout(() => resolve(), 2000);
@ -564,6 +565,10 @@ class CoWebsiteManager {
this.fire(); this.fire();
} }
if (coWebsite) {
iframeListener.unregisterIframe(coWebsite.iframe);
}
this.removeCoWebsiteFromStack(coWebsite); this.removeCoWebsiteFromStack(coWebsite);
resolve(); resolve();
}) })

View File

@ -147,7 +147,7 @@ const wa = {
* @deprecated Use WA.ui.openPopup instead * @deprecated Use WA.ui.openPopup instead
*/ */
openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[], popupClass : string, input : boolean): Popup { 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); return ui.openPopup(targetObject, message, buttons, popupClass, input);
}, },
/** /**

View File

@ -21,7 +21,7 @@
} }
aside { aside {
background: #212529; background: gray;
align-items: center; align-items: center;
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -1,8 +1,9 @@
@import "~nes.css/css/nes.min.css"; @import "~nes.css/css/nes.min.css";
@import "nes.custom.css"; @import "nes.custom.css";
@import "quill.snow.css";
@import "cowebsite.scss"; @import "cowebsite.scss";
@import "cowebsite-mobile.scss"; @import "cowebsite-mobile.scss";
@import "style"; @import "style";
@import "mobile-style.scss"; @import "mobile-style.scss";
@import "fonts.scss"; @import "fonts.scss";
@import "quill.snow.css"; @import "TextGlobalMessageSvelte-Style";

View File

@ -8,8 +8,7 @@ body img:focus,
body input:focus { body input:focus {
outline: -webkit-focus-ring-color auto 0; outline: -webkit-focus-ring-color auto 0;
} }
body .message-info{
body .message-info {
width: 20%; width: 20%;
height: auto; height: auto;
min-height: 30px; min-height: 30px;
@ -19,20 +18,17 @@ body .message-info {
padding-top: 10px; padding-top: 10px;
text-align: center; text-align: center;
} }
body .message-info.error{
body .message-info.error {
background: red; background: red;
} }
body .message-info.success{
body .message-info.success {
background: green; background: green;
} }
body .message-info.info{
body .message-info.info {
background: dodgerblue; background: dodgerblue;
} }
body .message-info.warning { body .message-info.warning{
background: #ffa500d6; background: #ffa500d6;
} }
@ -161,18 +157,16 @@ video.myCamVideo{
/*height: 113px;*/ /*height: 113px;*/
} }
.sound-progress { .sound-progress{
display: none; display: none;
position: absolute; position: absolute;
right: 14px; right: 14px;
top: calc(50% - 5px); top: calc(50% - 5px);
} }
.sound-progress.active{
.sound-progress.active {
display: table-column; display: table-column;
} }
.sound-progress span{
.sound-progress span {
position: absolute; position: absolute;
color: black; color: black;
background-color: #00000020; background-color: #00000020;
@ -180,28 +174,22 @@ video.myCamVideo{
height: 5px; height: 5px;
border-radius: 50%; border-radius: 50%;
} }
.sound-progress span.active{
.sound-progress span.active {
background-color: #00c3ff66 background-color: #00c3ff66
} }
.sound-progress span:nth-child(1){
.sound-progress span:nth-child(1) {
top: calc(50% + 20px); top: calc(50% + 20px);
} }
.sound-progress span:nth-child(2){
.sound-progress span:nth-child(2) {
top: calc(50% + 10px); top: calc(50% + 10px);
} }
.sound-progress span:nth-child(3){
.sound-progress span:nth-child(3) {
top: calc(50% - 0px); top: calc(50% - 0px);
} }
.sound-progress span:nth-child(4){
.sound-progress span:nth-child(4) {
top: calc(50% - 10px); top: calc(50% - 10px);
} }
.sound-progress span:nth-child(5){
.sound-progress span:nth-child(5) {
top: calc(50% - 20px); top: calc(50% - 20px);
} }
@ -232,39 +220,32 @@ video.myCamVideo{
transition-timing-function: ease-in-out; transition-timing-function: ease-in-out;
margin: 0 4%; margin: 0 4%;
} }
.btn-cam-action div.disabled { .btn-cam-action div.disabled {
background: #d75555; background: #d75555;
} }
.btn-cam-action div.enabled { .btn-cam-action div.enabled {
background: #73c973; background: #73c973;
} }
.btn-cam-action:hover div{
.btn-cam-action:hover div {
transform: translateY(0); transform: translateY(0);
} }
.btn-cam-action div:hover{
.btn-cam-action div:hover {
background: #407cf7; background: #407cf7;
box-shadow: 4px 4px 48px #666; box-shadow: 4px 4px 48px #666;
transition: 120ms; transition: 120ms;
} }
.btn-micro{
.btn-micro { pointer-events: auto;
pointer-events: none;
transition: all .3s; transition: all .3s;
/*right: 44px;*/ /*right: 44px;*/
} }
.btn-video{
.btn-video { pointer-events: auto;
pointer-events: none;
transition: all .25s; transition: all .25s;
/*right: 134px;*/ /*right: 134px;*/
} }
.btn-monitor{
.btn-monitor { pointer-events: auto;
pointer-events: none;
transition: all .2s; transition: all .2s;
/*right: 224px;*/ /*right: 224px;*/
} }
@ -290,26 +271,24 @@ video.myCamVideo{
right: 44px; right: 44px;
opacity: 1; opacity: 1;
} }
.btn-cam-action div img{
.btn-cam-action div img {
height: 22px; height: 22px;
width: 30px; width: 30px;
position: relative; position: relative;
pointer-events: all; pointer-events: all;
} }
/* Spinner */ /* Spinner */
.connecting-spinner { .connecting-spinner {
/*display: inline-block;*/ /*display: inline-block;*/
position: absolute; position: absolute;
left: calc(50% - 62px); left: calc(50% - 62px);
top: calc(50% - 62px); top: calc(50% - 62px);
width: 130px;
height: 130px;
}
width: 130px;
height: 130px;
}
.connecting-spinner:after { .connecting-spinner:after {
content: " "; content: " ";
display: block; display: block;
@ -321,7 +300,6 @@ video.myCamVideo{
border-color: #fff transparent #fff transparent; border-color: #fff transparent #fff transparent;
animation: connecting-spinner 1.2s linear infinite; animation: connecting-spinner 1.2s linear infinite;
} }
@keyframes connecting-spinner { @keyframes connecting-spinner {
0% { 0% {
transform: rotate(0deg); transform: rotate(0deg);
@ -331,14 +309,16 @@ video.myCamVideo{
} }
} }
.rtc-error { .rtc-error {
position: absolute; position: absolute;
left: calc(50% - 68px); left: calc(50% - 68px);
top: calc(50% - 68px); top: calc(50% - 68px);
width: 130px; width: 130px;
height: 130px; height: 130px;
} }
.rtc-error:after { .rtc-error:after {
content: " "; content: " ";
display: block; display: block;
@ -357,13 +337,11 @@ video.myCamVideo{
} }
/* New layout */ /* New layout */
body { body {
margin: 0; margin: 0;
height: 100%; height: 100%;
width: 100%; width: 100%;
} }
.main-container { .main-container {
height: 100%; height: 100%;
width: 100%; width: 100%;
@ -374,16 +352,20 @@ body {
.game-overlay { .game-overlay {
flex-direction: row; flex-direction: row;
} }
.sidebar { .sidebar {
flex-direction: column; flex-direction: column;
} }
.sidebar>div {
.sidebar > div {
max-height: 21%; max-height: 21%;
height: 21%;
} }
.sidebar>div:hover {
.sidebar > div:hover {
max-height: 25%; max-height: 25%;
} }
} }
#game { #game {
@ -398,7 +380,6 @@ body {
* Style Input Range * Style Input Range
* https://www.cssportal.com/style-input-range/ * https://www.cssportal.com/style-input-range/
*/ */
input[type=range] { input[type=range] {
height: 28px; height: 28px;
-webkit-appearance: none; -webkit-appearance: none;
@ -406,11 +387,9 @@ input[type=range] {
width: 100%; width: 100%;
background-color: transparent; background-color: transparent;
} }
input[type=range]:focus { input[type=range]:focus {
outline: none; outline: none;
} }
input[type=range]::-webkit-slider-runnable-track { input[type=range]::-webkit-slider-runnable-track {
width: 100%; width: 100%;
height: 5px; height: 5px;
@ -420,7 +399,6 @@ input[type=range]::-webkit-slider-runnable-track {
border-radius: 5px; border-radius: 5px;
border: 1px solid #000000; border: 1px solid #000000;
} }
input[type=range]::-webkit-slider-thumb { input[type=range]::-webkit-slider-thumb {
box-shadow: 1px 1px 1px #000000; box-shadow: 1px 1px 1px #000000;
border: 1px solid #000000; border: 1px solid #000000;
@ -431,11 +409,9 @@ input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none; -webkit-appearance: none;
margin-top: -8.5px; margin-top: -8.5px;
} }
input[type=range]:focus::-webkit-slider-runnable-track { input[type=range]:focus::-webkit-slider-runnable-track {
background: #FFFFFF; background: #FFFFFF;
} }
input[type=range]::-moz-range-track { input[type=range]::-moz-range-track {
width: 100%; width: 100%;
height: 5px; height: 5px;
@ -445,7 +421,6 @@ input[type=range]::-moz-range-track {
border-radius: 5px; border-radius: 5px;
border: 1px solid #000000; border: 1px solid #000000;
} }
input[type=range]::-moz-range-thumb { input[type=range]::-moz-range-thumb {
box-shadow: 1px 1px 1px #000000; box-shadow: 1px 1px 1px #000000;
border: 1px solid #000000; border: 1px solid #000000;
@ -454,7 +429,6 @@ input[type=range]::-moz-range-thumb {
border-radius: 5px; border-radius: 5px;
background: #FFFFFF; background: #FFFFFF;
} }
input[type=range]::-ms-track { input[type=range]::-ms-track {
width: 100%; width: 100%;
height: 5px; height: 5px;
@ -463,21 +437,18 @@ input[type=range]::-ms-track {
border-color: transparent; border-color: transparent;
color: transparent; color: transparent;
} }
input[type=range]::-ms-fill-lower { input[type=range]::-ms-fill-lower {
background: #FFFFFF; background: #FFFFFF;
border: 1px solid #000000; border: 1px solid #000000;
border-radius: 10px; border-radius: 10px;
box-shadow: 1px 1px 1px #000000; box-shadow: 1px 1px 1px #000000;
} }
input[type=range]::-ms-fill-upper { input[type=range]::-ms-fill-upper {
background: #FFFFFF; background: #FFFFFF;
border: 1px solid #000000; border: 1px solid #000000;
border-radius: 10px; border-radius: 10px;
box-shadow: 1px 1px 1px #000000; box-shadow: 1px 1px 1px #000000;
} }
input[type=range]::-ms-thumb { input[type=range]::-ms-thumb {
margin-top: 1px; margin-top: 1px;
box-shadow: 1px 1px 1px #000000; box-shadow: 1px 1px 1px #000000;
@ -487,15 +458,14 @@ input[type=range]::-ms-thumb {
border-radius: 5px; border-radius: 5px;
background: #FFFFFF; background: #FFFFFF;
} }
input[type=range]:focus::-ms-fill-lower { input[type=range]:focus::-ms-fill-lower {
background: #FFFFFF; background: #FFFFFF;
} }
input[type=range]:focus::-ms-fill-upper { input[type=range]:focus::-ms-fill-upper {
background: #FFFFFF; background: #FFFFFF;
} }
.game-overlay { .game-overlay {
display: none; display: none;
position: absolute; 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!!!) */ /* 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; pointer-events: none;
} }
.game-overlay+div>div { .game-overlay + div > div {
pointer-events: auto; pointer-events: auto;
} }
@ -529,7 +499,7 @@ input[type=range]:focus::-ms-fill-upper {
flex-wrap: wrap; flex-wrap: wrap;
} }
.main-section>div { .main-section > div {
margin: 2%; margin: 2%;
flex-basis: 96%; flex-basis: 96%;
transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s, flex-basis 0.2s; 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;*/ /*flex-shrink: 2;*/
} }
.main-section>div:hover { .main-section > div:hover {
margin: 0%; margin: 0%;
flex-basis: 100%; flex-basis: 100%;
} }
@ -547,7 +517,7 @@ input[type=range]:focus::-ms-fill-upper {
display: flex; display: flex;
} }
.sidebar>div { .sidebar > div {
margin: 2%; 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; 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; pointer-events: auto;
@ -557,12 +527,12 @@ input[type=range]:focus::-ms-fill-upper {
} }
} }
.sidebar>div:hover { .sidebar > div:hover {
margin: 0%; 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 { .media-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -572,20 +542,21 @@ input[type=range]:focus::-ms-fill-upper {
.chat-mode { .chat-mode {
display: grid; display: grid;
width: 100%; width: 100%;
align-items: flex-start; align-items: flex-start;
padding: 1%; padding: 1%;
} }
.chat-mode>div { .chat-mode > div {
margin: 1%; margin: 1%;
max-height: 96%; max-height: 96%;
transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s; 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%; margin: 0%;
} }
.chat-mode.one-col { .chat-mode.one-col {
grid-template-columns: repeat(1, 1fr); grid-template-columns: repeat(1, 1fr);
} }
@ -618,14 +589,14 @@ input[type=range]:focus::-ms-fill-upper {
border-radius: 0 0 15px 15px; border-radius: 0 0 15px 15px;
} }
.message-container { .message-container{
height: auto; height: auto;
border-radius: 0 0 10px 10px; border-radius: 0 0 10px 10px;
color: white; color: white;
top: 0; top: 0;
} }
.message-container .content-message { .message-container .content-message{
position: relative; position: relative;
padding: 20px; padding: 20px;
margin: 20px; margin: 20px;
@ -682,12 +653,10 @@ input[type=range]:focus::-ms-fill-upper {
/* VIDEO QUALITY */ /* VIDEO QUALITY */
.main-console div.setting h1{
.main-console div.setting h1 {
color: white; color: white;
} }
.main-console div.setting select{
.main-console div.setting select {
background: black; background: black;
color: white; color: white;
min-width: 280px; min-width: 280px;
@ -696,20 +665,17 @@ input[type=range]:focus::-ms-fill-upper {
padding: 10px; padding: 10px;
border-radius: 15px; border-radius: 15px;
} }
.main-console div.setting select:focus{
.main-console div.setting select:focus {
border: solid 1px white; border: solid 1px white;
outline: none; outline: none;
} }
.main-console div.setting.active section{
.main-console div.setting.active section {
display: block; display: block;
} }
/*REPORT input*/ /*REPORT input*/
div.modal-report-user{
div.modal-report-user {
position: absolute; position: absolute;
width: 800px; width: 800px;
height: 600px; height: 600px;
@ -719,7 +685,7 @@ div.modal-report-user {
border-radius: 15px; border-radius: 15px;
} }
.modal-report-user textarea { .modal-report-user textarea{
position: absolute; position: absolute;
height: 200px; height: 200px;
z-index: 999; z-index: 999;
@ -731,7 +697,7 @@ div.modal-report-user {
border-radius: 15px; border-radius: 15px;
} }
.modal-report-user img { .modal-report-user img{
position: absolute; position: absolute;
height: 24px; height: 24px;
width: 24px; width: 24px;
@ -740,7 +706,7 @@ div.modal-report-user {
top: 10px; top: 10px;
} }
.modal-report-user img#cancel-report-user { .modal-report-user img#cancel-report-user{
position: absolute; position: absolute;
z-index: 999; z-index: 999;
right: 0; right: 0;
@ -751,7 +717,7 @@ div.modal-report-user {
margin: 10px; margin: 10px;
} }
.modal-report-user button { .modal-report-user button{
position: absolute; position: absolute;
top: 450px; top: 450px;
left: calc(50% - 50px); left: calc(50% - 50px);
@ -770,7 +736,7 @@ div.modal-report-user {
transform: scale(1.1); transform: scale(1.1);
} }
.modal-report-user p#title-report-user { .modal-report-user p#title-report-user{
font-size: 30px; font-size: 30px;
color: white; color: white;
position: absolute; position: absolute;
@ -779,7 +745,7 @@ div.modal-report-user {
text-align: center; text-align: center;
} }
.modal-report-user p#body-report-user { .modal-report-user p#body-report-user{
font-size: 24px; font-size: 24px;
color: white; color: white;
position: absolute; position: absolute;
@ -787,29 +753,24 @@ div.modal-report-user {
width: 100%; width: 100%;
text-align: left; text-align: left;
padding: 30px; padding: 30px;
max-width: calc(800px - 60px); max-width: calc(800px - 60px); /* size of modal - padding*/
/* size of modal - padding*/
} }
/*MESSAGE*/ /*MESSAGE*/
.discussion{
.discussion {
position: fixed; position: fixed;
left: -300px; left: -300px;
top: 0px; top: 0px;
width: 280px; width: 220px;
height: 100%; height: 100%;
background-color: #333333; background-color: #333333;
padding: 20px; padding: 20px;
transition: all 0.5s ease; transition: all 0.5s ease;
} }
.discussion.active{
.discussion.active {
left: 0; left: 0;
} }
.discussion .active-btn{
.discussion .active-btn {
display: none; display: none;
height: 50px; height: 50px;
width: 50px; width: 50px;
@ -821,47 +782,43 @@ div.modal-report-user {
border: none; border: none;
transition: all 0.5s ease; transition: all 0.5s ease;
} }
.discussion .active-btn.active{
.discussion .active-btn.active {
display: block; display: block;
} }
.discussion .active-btn:hover { .discussion .active-btn:hover {
transform: scale(1.1) rotateY(3.142rad); transform: scale(1.1) rotateY(3.142rad);
} }
.discussion .active-btn img{
.discussion .active-btn img {
width: 26px; width: 26px;
height: 26px; height: 26px;
margin: 13px 5px; margin: 13px 5px;
} }
.discussion .close-btn { .discussion .close-btn{
position: absolute; position: absolute;
top: 0; top: 0;
right: 10px; right: 10px;
background: none; background: none;
border: none; border: none;
} }
.discussion .close-btn img{
.discussion .close-btn img {
height: 15px; height: 15px;
right: 15px; right: 15px;
} }
.discussion p { .discussion p{
color: white; color: white;
font-size: 12px; font-size: 12px;
padding-left: 10px; padding-left: 10px;
margin: 0; margin: 0;
} }
.discussion .participants { .discussion .participants{
height: 200px; height: 200px;
margin: 10px 0; margin: 10px 0;
} }
.discussion .participants .participant { .discussion .participants .participant{
display: flex; display: flex;
margin: 5px 10px; margin: 5px 10px;
background-color: #ffffff69; background-color: #ffffff69;
@ -869,11 +826,10 @@ div.modal-report-user {
border-radius: 15px; border-radius: 15px;
} }
.discussion .participants .participant:hover { .discussion .participants .participant:hover{
background-color: #ffffff; background-color: #ffffff;
} }
.discussion .participants .participant:hover p{
.discussion .participants .participant:hover p {
color: black; color: black;
} }
@ -888,12 +844,12 @@ div.modal-report-user {
margin-top: 18px; margin-top: 18px;
} }
.discussion .participants .participant img { .discussion .participants .participant img{
width: 26px; width: 26px;
height: 26px; height: 26px;
} }
.discussion .participants .participant p { .discussion .participants .participant p{
font-size: 16px; font-size: 16px;
margin-left: 10px; margin-left: 10px;
margin-top: 2px; margin-top: 2px;
@ -913,11 +869,11 @@ div.modal-report-user {
transition: all .5s ease; transition: all .5s ease;
} }
.discussion .participants .participant:hover button.report-btn { .discussion .participants .participant:hover button.report-btn{
width: 70px; width: 70px;
} }
.discussion .messages { .discussion .messages{
position: absolute; position: absolute;
height: calc(100% - 390px); height: calc(100% - 390px);
overflow-x: hidden; overflow-x: hidden;
@ -926,28 +882,28 @@ div.modal-report-user {
width: calc(100% - 40px); width: calc(100% - 40px);
} }
.discussion .messages h2 { .discussion .messages h2{
color: white; color: white;
font-size: 12px; font-size: 12px;
} }
.discussion .messages .message { .discussion .messages .message{
margin: 5px 0px; margin: 5px 0px;
float: right; float: right;
text-align: right; text-align: right;
width: 100%; width: 100%;
} }
.discussion .messages .message.me { .discussion .messages .message.me{
float: left; float: left;
text-align: left; text-align: left;
} }
.discussion .messages .message p { .discussion .messages .message p{
font-size: 10px; font-size: 10px;
} }
.discussion .messages .message p.body { .discussion .messages .message p.body{
color: black; color: black;
font-size: 12px; font-size: 12px;
white-space: pre-wrap; white-space: pre-wrap;
@ -957,8 +913,7 @@ div.modal-report-user {
margin-top: 5px; margin-top: 5px;
margin-bottom: 12px; margin-bottom: 12px;
} }
.discussion .messages .message p a{
.discussion .messages .message p a {
color: white; color: white;
} }
@ -974,7 +929,7 @@ div.modal-report-user {
margin-bottom: 10px; margin-bottom: 10px;
} }
.discussion .send-message input { .discussion .send-message input{
position: absolute; position: absolute;
width: calc(100% - 20px); width: calc(100% - 20px);
height: 30px; height: 30px;
@ -985,22 +940,20 @@ div.modal-report-user {
padding: 6px; padding: 6px;
} }
.discussion .send-message img { .discussion .send-message img{
position: absolute; position: absolute;
margin-right: 10px; margin-right: 10px;
width: 20px; width: 20px;
height: 20px; height: 20px;
background-color: #ffffff69; background-color: #ffffff69;
} }
.discussion .send-message img:hover{
.discussion .send-message img:hover {
background-color: #ffffff; background-color: #ffffff;
} }
/** Action button **/ /** Action button **/
div.action{
div.action {
position: absolute; position: absolute;
height: auto; height: auto;
text-align: center; text-align: center;
@ -1011,10 +964,9 @@ div.action {
animation-iteration-count: infinite; animation-iteration-count: infinite;
animation-timing-function: ease-in-out; animation-timing-function: ease-in-out;
} }
div.action.info, div.action.info,
div.action.warning, div.action.warning,
div.action.danger { div.action.danger{
transition: all 1s ease; transition: all 1s ease;
animation: mymove 1s; animation: mymove 1s;
animation-iteration-count: infinite; animation-iteration-count: infinite;
@ -1028,18 +980,15 @@ div.action p.action-body{
width: 350px; width: 350px;
max-width: 350px; max-width: 350px;
} }
div.action.warning p.action-body{
div.action.warning p.action-body {
background-color: #ff9800eb; background-color: #ff9800eb;
color: #000; color: #000;
} }
div.action.danger p.action-body{
div.action.danger p.action-body {
background-color: #da0000e3; background-color: #da0000e3;
color: #000; color: #000;
} }
.popUpElement{
.popUpElement {
font-family: 'Press Start 2P'; font-family: 'Press Start 2P';
text-align: left; text-align: left;
color: white; color: white;
@ -1064,8 +1013,10 @@ div.action.danger p.action-body {
.popUpElement .buttonContainer { .popUpElement .buttonContainer {
float: right; float: right;
background-color: inherit; background-color: inherit;
} }
@keyframes mymove { @keyframes mymove {
0% {bottom: 40px;} 0% {bottom: 40px;}
50% {bottom: 30px;} 50% {bottom: 30px;}
@ -1115,9 +1066,6 @@ div.is-silent.hide {
right: 15px; right: 15px;
} }
.emote-menu .emoji-picker .emoji-picker__emoji {
font-family: "Twemoji Mozilla" !important;
}
.popUpElement.overlay-text { .popUpElement.overlay-text {

View File

@ -7,7 +7,6 @@ import MiniCssExtractPlugin from "mini-css-extract-plugin";
import sveltePreprocess from "svelte-preprocess"; import sveltePreprocess from "svelte-preprocess";
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin"; import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
import NodePolyfillPlugin from "node-polyfill-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 mode = process.env.NODE_ENV ?? "development";
const buildNpmTypingsForApi = !!process.env.BUILD_TYPINGS; const buildNpmTypingsForApi = !!process.env.BUILD_TYPINGS;
@ -208,6 +207,8 @@ module.exports = {
POSTHOG_API_KEY: null, POSTHOG_API_KEY: null,
POSTHOG_URL: null, POSTHOG_URL: null,
NODE_ENV: mode, NODE_ENV: mode,
DISABLE_ANONYMOUS: false,
OPID_LOGIN_SCREEN_PROVIDER: null,
}), }),
], ],
} as Configuration & WebpackDevServer.Configuration; } as Configuration & WebpackDevServer.Configuration;

View File

@ -1,16 +1,18 @@
WA.onInit().then(() => { WA.onInit().then(() => {
let message; let message;
WA.room.onEnterZone("carpet", () => { WA.room.onEnterLayer("carpet").subscribe(() => {
message = WA.ui.displayActionMessage({ message = WA.ui.displayActionMessage({
message: "This is a test message. Press space to display a chat message. Walk out to hide the message.", message:
callback: () => { "This is a test message. Press space to display a chat message. Walk out to hide the message.",
WA.chat.sendChatMessage("Hello world!", "The bot"); callback: () => {
} WA.chat.sendChatMessage("Hello world!", "The bot");
}); },
}); });
});
WA.room.onLeaveZone("carpet", () => {
message && message.remove(); WA.room.onLeaveLayer("carpet").subscribe(() => {
}); message && message.remove();
});
}); });

View File

@ -6,6 +6,7 @@ import { PrometheusController } from "./Controller/PrometheusController";
import { DebugController } from "./Controller/DebugController"; import { DebugController } from "./Controller/DebugController";
import { App as uwsApp } from "./Server/sifrr.server"; import { App as uwsApp } from "./Server/sifrr.server";
import { AdminController } from "./Controller/AdminController"; import { AdminController } from "./Controller/AdminController";
import { OpenIdProfileController } from "./Controller/OpenIdProfileController";
class App { class App {
public app: uwsApp; public app: uwsApp;
@ -15,6 +16,7 @@ class App {
public prometheusController: PrometheusController; public prometheusController: PrometheusController;
private debugController: DebugController; private debugController: DebugController;
private adminController: AdminController; private adminController: AdminController;
private openIdProfileController: OpenIdProfileController;
constructor() { constructor() {
this.app = new uwsApp(); this.app = new uwsApp();
@ -26,6 +28,7 @@ class App {
this.prometheusController = new PrometheusController(this.app); this.prometheusController = new PrometheusController(this.app);
this.debugController = new DebugController(this.app); this.debugController = new DebugController(this.app);
this.adminController = new AdminController(this.app); this.adminController = new AdminController(this.app);
this.openIdProfileController = new OpenIdProfileController(this.app);
} }
} }

View File

@ -1,11 +1,11 @@
import { v4 } from "uuid"; import { v4 } from "uuid";
import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js"; import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
import { BaseController } from "./BaseController"; import { BaseController } from "./BaseController";
import { adminApi } from "../Services/AdminApi"; import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager"; import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
import { parse } from "query-string"; import { parse } from "query-string";
import { openIDClient } from "../Services/OpenIDClient"; import { openIDClient } from "../Services/OpenIDClient";
import { DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable" import { DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable";
export interface TokenInterface { export interface TokenInterface {
userUuid: string; userUuid: string;
@ -56,19 +56,20 @@ export class AuthenticateController extends BaseController {
res.onAborted(() => { res.onAborted(() => {
console.warn("/message request was aborted"); 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 { try {
//verify connected by token //verify connected by token
if (token != undefined) { if (token != undefined) {
try { try {
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false); 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"); throw Error("Token cannot to be check on Hydra");
} }
await openIDClient.checkTokenAuth(authTokenData.hydraAccessToken); const resCheckTokenAuth = await openIDClient.checkTokenAuth(authTokenData.accessToken);
res.writeStatus("200"); res.writeStatus("200");
this.addCorsHeaders(res); 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) { } catch (err) {
console.info("User was not connected", 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"); throw new Error("No sub in the response");
} }
const authToken = jwtTokenManager.createAuthToken(sub, userInfo.access_token, userInfo.username); 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"); res.writeStatus("200");
this.addCorsHeaders(res); 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) { } catch (e) {
console.error("openIDCallback => ERROR", e); console.error("openIDCallback => ERROR", e);
return this.errorToResponse(e, res); return this.errorToResponse(e, res);
@ -100,10 +106,10 @@ export class AuthenticateController extends BaseController {
try { try {
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false); 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"); throw Error("Token cannot to be logout on Hydra");
} }
await openIDClient.logoutUser(authTokenData.hydraAccessToken); await openIDClient.logoutUser(authTokenData.accessToken);
} catch (error) { } catch (error) {
console.error("openIDCallback => logout-callback", error); console.error("openIDCallback => logout-callback", error);
} finally { } finally {
@ -202,20 +208,20 @@ export class AuthenticateController extends BaseController {
res.onAborted(() => { res.onAborted(() => {
console.warn("/message request was aborted"); console.warn("/message request was aborted");
}); });
const { userIdentify, token } = parse(req.getQuery()); const { token } = parse(req.getQuery());
try { try {
//verify connected by token //verify connected by token
if (token != undefined) { if (token != undefined) {
try { try {
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false); 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"); throw Error("Token cannot to be check on Hydra");
} }
await openIDClient.checkTokenAuth(authTokenData.hydraAccessToken); await openIDClient.checkTokenAuth(authTokenData.accessToken);
//get login profile //get login profile
res.writeStatus("302"); res.writeStatus("302");
res.writeHeader("Location", adminApi.getProfileUrl(authTokenData.hydraAccessToken)); res.writeHeader("Location", adminApi.getProfileUrl(authTokenData.accessToken));
this.addCorsHeaders(res); this.addCorsHeaders(res);
// eslint-disable-next-line no-unsafe-finally // eslint-disable-next-line no-unsafe-finally
return res.end(); 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;
}
} }

View File

@ -26,10 +26,9 @@ import { jwtTokenManager, tokenInvalidException } from "../Services/JWTTokenMana
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi"; import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
import { SocketManager, socketManager } from "../Services/SocketManager"; import { SocketManager, socketManager } from "../Services/SocketManager";
import { emitInBatch } from "../Services/IoSocketHelpers"; 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 { Zone } from "_Model/Zone";
import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface"; import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface";
import { v4 } from "uuid";
import { CharacterTexture } from "../Services/AdminApi/CharacterTexture"; import { CharacterTexture } from "../Services/AdminApi/CharacterTexture";
export class IoSocketController { export class IoSocketController {
@ -48,15 +47,19 @@ export class IoSocketController {
const websocketProtocol = req.getHeader("sec-websocket-protocol"); const websocketProtocol = req.getHeader("sec-websocket-protocol");
const websocketExtensions = req.getHeader("sec-websocket-extensions"); const websocketExtensions = req.getHeader("sec-websocket-extensions");
const token = query.token; const token = query.token;
if (token !== ADMIN_API_TOKEN) { let authorizedRoomIds: string[];
console.log("Admin access refused for token: " + token); 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"); res.writeStatus("401 Unauthorized").end("Incorrect token");
return; return;
} }
const roomId = query.roomId; const roomId = query.roomId;
if (typeof roomId !== "string") { if (typeof roomId !== "string" || !authorizedRoomIds.includes(roomId)) {
console.error("Received"); console.error("Invalid room id");
res.writeStatus("400 Bad Request").end("Missing room id"); res.writeStatus("403 Bad Request").end("Invalid room id");
return; return;
} }
@ -70,8 +73,6 @@ export class IoSocketController {
}, },
message: (ws, arrayBuffer, isBinary): void => { message: (ws, arrayBuffer, isBinary): void => {
try { try {
const roomId = ws.roomId as string;
//TODO refactor message type and data //TODO refactor message type and data
const message: { event: string; message: { type: string; message: unknown; userUuid: string } } = const message: { event: string; message: { type: string; message: unknown; userUuid: string } } =
JSON.parse(new TextDecoder("utf-8").decode(new Uint8Array(arrayBuffer))); JSON.parse(new TextDecoder("utf-8").decode(new Uint8Array(arrayBuffer)));
@ -250,7 +251,7 @@ export class IoSocketController {
roomId roomId
); );
console.error(e); console.error(e);
throw new Error("User cannot access this room"); throw new Error("User cannot access this world");
} }
} }

View File

@ -52,7 +52,7 @@ export class MapController extends BaseController {
return; return;
} }
const mapUrl = roomUrl.protocol + "//" + match[ 1 ]; const mapUrl = roomUrl.protocol + "//" + match[1];
res.writeStatus("200 OK"); res.writeStatus("200 OK");
this.addCorsHeaders(res); this.addCorsHeaders(res);
@ -65,7 +65,7 @@ export class MapController extends BaseController {
tags: [], tags: [],
textures: [], textures: [],
contactPage: undefined, contactPage: undefined,
authenticationMandatory : DISABLE_ANONYMOUS, authenticationMandatory: DISABLE_ANONYMOUS,
} as MapDetailsData) } as MapDetailsData)
); );
@ -90,7 +90,7 @@ export class MapController extends BaseController {
const mapDetails = await adminApi.fetchMapDetails(query.playUri as string, userId); const mapDetails = await adminApi.fetchMapDetails(query.playUri as string, userId);
if (isMapDetailsData(mapDetails) && DISABLE_ANONYMOUS) { if (isMapDetailsData(mapDetails) && DISABLE_ANONYMOUS) {
(mapDetails as MapDetailsData).authenticationMandatory = true; mapDetails.authenticationMandatory = true;
} }
res.writeStatus("200 OK"); res.writeStatus("200 OK");

View 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>
`;
}
}

View File

@ -4,6 +4,7 @@ const API_URL = process.env.API_URL || "";
const ADMIN_API_URL = process.env.ADMIN_API_URL || ""; const ADMIN_API_URL = process.env.ADMIN_API_URL || "";
const ADMIN_URL = process.env.ADMIN_URL || ""; const ADMIN_URL = process.env.ADMIN_URL || "";
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || "myapitoken"; 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 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_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL;
const JITSI_ISS = process.env.JITSI_ISS || ""; 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 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 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 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 { export {
SECRET_KEY, SECRET_KEY,

View File

@ -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 Axios from "axios";
import { GameRoomPolicyTypes } from "_Model/PusherRoom"; import { GameRoomPolicyTypes } from "_Model/PusherRoom";
import { CharacterTexture } from "./AdminApi/CharacterTexture"; import { CharacterTexture } from "./AdminApi/CharacterTexture";
@ -142,13 +142,19 @@ class AdminApi {
}); });
} }
/*TODO add constant to use profile companny*/ /**
*
* @param accessToken
*/
getProfileUrl(accessToken: string): string { getProfileUrl(accessToken: string): string {
if (!ADMIN_URL) { if (!OPID_PROFILE_SCREEN_PROVIDER) {
throw new Error("No admin backoffice set!"); 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}`);
} }
} }

View File

@ -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 { uuid } from "uuidv4";
import Jwt, { verify } from "jsonwebtoken"; import Jwt, { verify } from "jsonwebtoken";
import { TokenInterface } from "../Controller/AuthenticateController"; import { TokenInterface } from "../Controller/AuthenticateController";
@ -6,14 +6,21 @@ import { adminApi, AdminBannedData } from "../Services/AdminApi";
export interface AuthTokenData { export interface AuthTokenData {
identifier: string; //will be a sub (id) if logged in or an uuid if anonymous identifier: string; //will be a sub (id) if logged in or an uuid if anonymous
hydraAccessToken?: string; accessToken?: string;
username?: string; username?: string;
} }
export interface AdminSocketTokenData {
authorizedRoomIds: string[]; //the list of rooms the client is authorized to read from.
}
export const tokenInvalidException = "tokenInvalid"; export const tokenInvalidException = "tokenInvalid";
class JWTTokenManager { class JWTTokenManager {
public createAuthToken(identifier: string, hydraAccessToken?: string, username?: string) { public verifyAdminSocketToken(token: string): AdminSocketTokenData {
return Jwt.sign({ identifier, hydraAccessToken, username }, SECRET_KEY, { expiresIn: "30d" }); 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 { public verifyJWTToken(token: string, ignoreExpiration: boolean = false): AuthTokenData {

View File

@ -1,18 +1,21 @@
import { Issuer, Client, IntrospectionResponse } from "openid-client"; import { Issuer, Client, IntrospectionResponse } from "openid-client";
import { OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_CLIENT_ISSUER, FRONT_URL } from "../Enum/EnvironmentVariable"; import {
OPID_CLIENT_ID,
const opidRedirectUri = FRONT_URL + "/jwt"; OPID_CLIENT_SECRET,
OPID_CLIENT_ISSUER,
OPID_CLIENT_REDIRECT_URL,
} from "../Enum/EnvironmentVariable";
class OpenIDClient { class OpenIDClient {
private issuerPromise: Promise<Client> | null = null; private issuerPromise: Promise<Client> | null = null;
private initClient(): Promise<Client> { private initClient(): Promise<Client> {
if (!this.issuerPromise) { 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({ return new issuer.Client({
client_id: OIDC_CLIENT_ID, client_id: OPID_CLIENT_ID,
client_secret: OIDC_CLIENT_SECRET, client_secret: OPID_CLIENT_SECRET,
redirect_uris: [opidRedirectUri], redirect_uris: [OPID_CLIENT_REDIRECT_URL],
response_types: ["code"], 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 }> { public getUserInfo(code: string, nonce: string): Promise<{ email: string; sub: string; access_token: string; username: string }> {
return this.initClient().then((client) => { 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 client.userinfo(tokenSet).then((res) => {
return { return {
...res, ...res,

View File

@ -54,10 +54,10 @@ import { CharacterTexture } from "./AdminApi/CharacterTexture";
const debug = Debug("socket"); const debug = Debug("socket");
interface AdminSocketRoomsList { interface AdminSocketRoomsList {
[ index: string ]: number; [index: string]: number;
} }
interface AdminSocketUsersList { interface AdminSocketUsersList {
[ index: string ]: boolean; [index: string]: boolean;
} }
export interface AdminSocketData { export interface AdminSocketData {
@ -635,7 +635,7 @@ export class SocketManager implements ZoneEventListener {
if (playGlobalMessageEvent.getBroadcasttoworld()) { if (playGlobalMessageEvent.getBroadcasttoworld()) {
tabUrlRooms = await adminApi.getUrlRoomsFromSameWorld(clientRoomUrl); tabUrlRooms = await adminApi.getUrlRoomsFromSameWorld(clientRoomUrl);
} else { } else {
tabUrlRooms = [ clientRoomUrl ]; tabUrlRooms = [clientRoomUrl];
} }
const roomMessage = new AdminRoomMessage(); const roomMessage = new AdminRoomMessage();