Merge branch 'develop' of github.com:thecodingmachine/workadventure into move-to-improvements

develop
David Négrier 2022-02-11 16:58:40 +01:00
commit 0b82df0d41
25 changed files with 1110 additions and 676 deletions

View File

@ -1,20 +1,118 @@
# Security
#
SECRET_KEY=
ADMIN_API_TOKEN=
#
# Networking
#
# The base domain
DOMAIN=workadventure.localhost
DEBUG_MODE=false
# Subdomains
# MUST match the DOMAIN variable above
FRONT_HOST=front.workadventure.localhost
PUSHER_HOST=pusher.workadventure.localhost
BACK_HOST=api.workadventure.localhost
MAPS_HOST=maps.workadventure.localhost
ICON_HOST=icon.workadventure.localhost
# SAAS admin panel
ADMIN_API_URL=
#
# Basic configuration
#
# The directory to store data in
DATA_DIR=./wa
# The URL used by default, in the form: "/_/global/map/url.json"
START_ROOM_URL=/_/global/maps.workadventu.re/Floor0/floor0.json
# 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=
MAX_PER_GROUP=4
MAX_USERNAME_LENGTH=8
DISABLE_ANONYMOUS=false
# The version of the docker image to use
# MUST uncomment "image" keys in the docker-compose file for it to be effective
VERSION=master
TZ=Europe/Paris
#
# Jitsi
#
JITSI_URL=meet.jit.si
# If your Jitsi environment has authentication set up, you MUST set JITSI_PRIVATE_MODE to "true" and you MUST pass a SECRET_JITSI_KEY to generate the JWT secret
# If your Jitsi environment has authentication set up,
# you MUST set JITSI_PRIVATE_MODE to "true"
# and you MUST pass a SECRET_JITSI_KEY to generate the JWT secret
JITSI_PRIVATE_MODE=false
JITSI_ISS=
SECRET_JITSI_KEY=
#
# Turn/Stun
#
# URL of the TURN server (needed to "punch a hole" through some networks for P2P connections)
TURN_SERVER=
TURN_USER=
TURN_PASSWORD=
# If your Turn server is configured to use the Turn REST API, you MUST put the shared auth secret here.
# If you are using Coturn, this is the value of the "static-auth-secret" parameter in your coturn config file.
# Keep empty if you are sharing hard coded / clear text credentials.
TURN_STATIC_AUTH_SECRET=
# URL of the STUN server
STUN_SERVER=
# The URL used by default, in the form: "/_/global/map/url.json"
START_ROOM_URL=/_/global/maps.workadventu.re/Floor0/floor0.json
#
# Certificate config
#
# The email address used by Let's encrypt to send renewal warnings (compulsory)
ACME_EMAIL=
#
# Additional app configs
# Configuration for apps which are not workadventure itself
#
# openID
OPID_CLIENT_ID=
OPID_CLIENT_SECRET=
OPID_CLIENT_ISSUER=
OPID_CLIENT_REDIRECT_URL=
OPID_LOGIN_SCREEN_PROVIDER=http://pusher.workadventure.localhost/login-screen
OPID_PROFILE_SCREEN_PROVIDER=
#
# Advanced configuration
# Generally does not need to be changed
#
# Networking
HTTP_PORT=80
HTTPS_PORT=443
# Workadventure settings
DISABLE_NOTIFICATIONS=false
SKIP_RENDER_OPTIMIZATIONS=false
STORE_VARIABLES_FOR_LOCAL_MAPS=true
# Debugging options
DEBUG_MODE=false
LOG_LEVEL=WARN
# Internal URLs
API_URL=back:50051
RESTART_POLICY=unless-stopped

View File

@ -1,114 +1,128 @@
version: "3.3"
version: "3.5"
services:
reverse-proxy:
image: traefik:v2.3
image: traefik:v2.6
command:
- --log.level=WARN
#- --api.insecure=true
- --log.level=${LOG_LEVEL}
- --providers.docker
- --entryPoints.web.address=:80
# Entry points
- --entryPoints.web.address=:${HTTP_PORT}
- --entrypoints.web.http.redirections.entryPoint.to=websecure
- --entrypoints.web.http.redirections.entryPoint.scheme=https
- --entryPoints.websecure.address=:443
- --entryPoints.websecure.address=:${HTTPS_PORT}
# HTTP challenge
- --certificatesresolvers.myresolver.acme.email=${ACME_EMAIL}
- --certificatesresolvers.myresolver.acme.storage=/acme.json
# used during the challenge
- --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web
# Let's Encrypt's staging server
# uncomment during testing to avoid rate limiting
#- --certificatesresolvers.dnsresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
ports:
- "80:80"
- "443:443"
# The Web UI (enabled by --api.insecure=true)
#- "8080:8080"
depends_on:
- pusher
- front
- "${HTTP_PORT}:80"
- "${HTTPS_PORT}:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./acme.json:/acme.json
restart: unless-stopped
- ${DATA_DIR}/letsencrypt/acme.json:/acme.json
restart: ${RESTART_POLICY}
front:
build:
context: ../..
dockerfile: front/Dockerfile
#image: thecodingmachine/workadventure-front:master
#image: thecodingmachine/workadventure-front:${VERSION}
environment:
DEBUG_MODE: "$DEBUG_MODE"
JITSI_URL: $JITSI_URL
JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE"
PUSHER_URL: //pusher.${DOMAIN}
ICON_URL: //icon.${DOMAIN}
TURN_SERVER: "${TURN_SERVER}"
TURN_USER: "${TURN_USER}"
TURN_PASSWORD: "${TURN_PASSWORD}"
START_ROOM_URL: "${START_ROOM_URL}"
- DEBUG_MODE
- JITSI_URL
- JITSI_PRIVATE_MODE
- PUSHER_URL=//${PUSHER_HOST}
- ICON_URL=//${ICON_HOST}
- TURN_SERVER
- TURN_USER
- TURN_PASSWORD
- TURN_STATIC_AUTH_SECRET
- STUN_SERVER
- START_ROOM_URL
- SKIP_RENDER_OPTIMIZATIONS
- MAX_PER_GROUP
- MAX_USERNAME_LENGTH
- DISABLE_ANONYMOUS
- DISABLE_NOTIFICATIONS
labels:
- "traefik.http.routers.front.rule=Host(`play.${DOMAIN}`)"
- "traefik.http.routers.front.entryPoints=web,traefik"
- "traefik.http.routers.front.rule=Host(`${FRONT_HOST}`)"
- "traefik.http.routers.front.entryPoints=web"
- "traefik.http.services.front.loadbalancer.server.port=80"
- "traefik.http.routers.front-ssl.rule=Host(`play.${DOMAIN}`)"
- "traefik.http.routers.front-ssl.rule=Host(`${FRONT_HOST}`)"
- "traefik.http.routers.front-ssl.entryPoints=websecure"
- "traefik.http.routers.front-ssl.tls=true"
- "traefik.http.routers.front-ssl.service=front"
- "traefik.http.routers.front-ssl.tls=true"
- "traefik.http.routers.front-ssl.tls.certresolver=myresolver"
restart: unless-stopped
restart: ${RESTART_POLICY}
pusher:
build:
context: ../..
dockerfile: pusher/Dockerfile
#image: thecodingmachine/workadventure-pusher:master
#image: thecodingmachine/workadventure-pusher:${VERSION}
command: yarn run runprod
environment:
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
SECRET_KEY: yourSecretKey
API_URL: back:50051
JITSI_URL: $JITSI_URL
JITSI_ISS: $JITSI_ISS
FRONT_URL: https://play.${DOMAIN}
- SECRET_JITSI_KEY
- SECRET_KEY
- API_URL
- FRONT_URL=https://${FRONT_HOST}
- JITSI_URL
- JITSI_ISS
- DISABLE_ANONYMOUS
labels:
- "traefik.http.routers.pusher.rule=Host(`pusher.${DOMAIN}`)"
- "traefik.http.routers.pusher.entryPoints=web,traefik"
- "traefik.http.routers.pusher.rule=Host(`${PUSHER_HOST}`)"
- "traefik.http.routers.pusher.entryPoints=web"
- "traefik.http.services.pusher.loadbalancer.server.port=8080"
- "traefik.http.routers.pusher-ssl.rule=Host(`pusher.${DOMAIN}`)"
- "traefik.http.routers.pusher-ssl.rule=Host(${PUSHER_HOST}`)"
- "traefik.http.routers.pusher-ssl.entryPoints=websecure"
- "traefik.http.routers.pusher-ssl.tls=true"
- "traefik.http.routers.pusher-ssl.service=pusher"
- "traefik.http.routers.pusher-ssl.tls=true"
- "traefik.http.routers.pusher-ssl.tls.certresolver=myresolver"
restart: unless-stopped
restart: ${RESTART_POLICY}
back:
build:
context: ../..
dockerfile: back/Dockerfile
#image: thecodingmachine/workadventure-back:master
#image: thecodingmachine/workadventure-back:${VERSION}
command: yarn run runprod
environment:
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
ADMIN_API_URL: "$ADMIN_API_URL"
JITSI_URL: $JITSI_URL
JITSI_ISS: $JITSI_ISS
- SECRET_JITSI_KEY
- SECRET_KEY
- ADMIN_API_TOKEN
- ADMIN_API_URL
- TURN_SERVER
- TURN_USER
- TURN_PASSWORD
- TURN_STATIC_AUTH_SECRET
- STUN_SERVER
- JITSI_URL
- JITSI_ISS
- MAX_PER_GROUP
- STORE_VARIABLES_FOR_LOCAL_MAPS
labels:
- "traefik.http.routers.back.rule=Host(`api.${DOMAIN}`)"
- "traefik.http.routers.back.rule=Host(`${BACK_HOST}`)"
- "traefik.http.routers.back.entryPoints=web"
- "traefik.http.services.back.loadbalancer.server.port=8080"
- "traefik.http.routers.back-ssl.rule=Host(`api.${DOMAIN}`)"
- "traefik.http.routers.back-ssl.rule=Host(`${BACK_HOST}`)"
- "traefik.http.routers.back-ssl.entryPoints=websecure"
- "traefik.http.routers.back-ssl.tls=true"
- "traefik.http.routers.back-ssl.service=back"
- "traefik.http.routers.back-ssl.tls=true"
- "traefik.http.routers.back-ssl.tls.certresolver=myresolver"
restart: unless-stopped
restart: ${RESTART_POLICY}
icon:
image: matthiasluedtke/iconserver:v3.13.0
labels:
- "traefik.http.routers.icon.rule=Host(`icon.${DOMAIN}`)"
- "traefik.http.routers.icon.rule=Host(`${ICON_HOST}`)"
- "traefik.http.routers.icon.entryPoints=web,traefik"
- "traefik.http.services.icon.loadbalancer.server.port=8080"
- "traefik.http.routers.icon-ssl.rule=Host(`icon.${DOMAIN}`)"
- "traefik.http.routers.icon-ssl.rule=Host(`${ICON_HOST}`)"
- "traefik.http.routers.icon-ssl.entryPoints=websecure"
- "traefik.http.routers.icon-ssl.tls=true"
- "traefik.http.routers.icon-ssl.service=icon"
- "traefik.http.routers.icon-ssl.tls=true"
- "traefik.http.routers.icon-ssl.tls.certresolver=myresolver"

View File

@ -2,9 +2,10 @@
import { onMount } from "svelte";
import { ICON_URL } from "../../Enum/EnvironmentVariable";
import { coWebsitesNotAsleep, mainCoWebsite } from "../../Stores/CoWebsiteStore";
import { mainCoWebsite } from "../../Stores/CoWebsiteStore";
import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore";
import type { CoWebsite } from "../../WebRtc/CoWebsiteManager";
import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite";
import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite";
import { iframeStates } from "../../WebRtc/CoWebsiteManager";
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
@ -14,16 +15,15 @@
let icon: HTMLImageElement;
let iconLoaded = false;
let state = coWebsite.state;
const coWebsiteUrl = coWebsite.iframe.src;
const urlObject = new URL(coWebsiteUrl);
let state = coWebsite.getStateSubscriber();
let isJitsi: boolean = coWebsite instanceof JitsiCoWebsite;
const mainState = coWebsiteManager.getMainStateSubscriber();
onMount(() => {
icon.src = coWebsite.jitsi
icon.src = isJitsi
? "/resources/logos/meet.svg"
: `${ICON_URL}/icon?url=${urlObject.hostname}&size=64..96..256&fallback_icon_color=14304c`;
icon.alt = coWebsite.altMessage ?? urlObject.hostname;
: `${ICON_URL}/icon?url=${coWebsite.getUrl().hostname}&size=64..96..256&fallback_icon_color=14304c`;
icon.alt = coWebsite.getUrl().hostname;
icon.onload = () => {
iconLoaded = true;
};
@ -33,21 +33,24 @@
if (vertical) {
coWebsiteManager.goToMain(coWebsite);
} else if ($mainCoWebsite) {
if ($mainCoWebsite.iframe.id === coWebsite.iframe.id) {
const coWebsites = $coWebsitesNotAsleep;
const newMain = $highlightedEmbedScreen ?? coWebsites.length > 1 ? coWebsites[1] : undefined;
if (newMain && newMain.iframe.id !== $mainCoWebsite.iframe.id) {
coWebsiteManager.goToMain(newMain);
} else if (coWebsiteManager.getMainState() === iframeStates.closed) {
if ($mainCoWebsite.getId() === coWebsite.getId()) {
if (coWebsiteManager.getMainState() === iframeStates.closed) {
coWebsiteManager.displayMain();
} else if ($highlightedEmbedScreen?.type === "cowebsite") {
coWebsiteManager.goToMain($highlightedEmbedScreen.embed);
} else {
coWebsiteManager.hideMain();
}
} else {
highlightedEmbedScreen.toggleHighlight({
type: "cowebsite",
embed: coWebsite,
});
if (coWebsiteManager.getMainState() === iframeStates.closed) {
coWebsiteManager.goToMain(coWebsite);
coWebsiteManager.displayMain();
} else {
highlightedEmbedScreen.toggleHighlight({
type: "cowebsite",
embed: coWebsite,
});
}
}
}
@ -65,11 +68,14 @@
let isHighlight: boolean = false;
let isMain: boolean = false;
$: {
isMain = $mainCoWebsite !== undefined && $mainCoWebsite.iframe === coWebsite.iframe;
isMain =
$mainState === iframeStates.opened &&
$mainCoWebsite !== undefined &&
$mainCoWebsite.getId() === coWebsite.getId();
isHighlight =
$highlightedEmbedScreen !== null &&
$highlightedEmbedScreen.type === "cowebsite" &&
$highlightedEmbedScreen.embed.iframe === coWebsite.iframe;
$highlightedEmbedScreen.embed.getId() === coWebsite.getId();
}
</script>
@ -86,7 +92,7 @@
<img
class="cowebsite-icon noselect nes-pointer"
class:hide={!iconLoaded}
class:jitsi={coWebsite.jitsi}
class:jitsi={isJitsi}
bind:this={icon}
on:dragstart|preventDefault={noDrag}
alt=""
@ -213,7 +219,8 @@
}
&:not(.vertical) {
animation: bounce 0.35s ease 6 alternate;
transition: all 300ms;
transform: translateY(0px);
}
&.vertical {
@ -234,7 +241,7 @@
&.displayed {
&:not(.vertical) {
animation: activeThumbnail 300ms ease-in 0s forwards;
transform: translateY(-15px);
}
}
@ -263,16 +270,6 @@
}
}
@keyframes activeThumbnail {
0% {
transform: translateY(0);
}
100% {
transform: translateY(-15px);
}
}
@keyframes bounce {
from {
transform: translateY(0);

View File

@ -7,7 +7,7 @@
{#if $coWebsites.length > 0}
<div id="cowebsite-thumbnail-container" class:vertical>
{#each [...$coWebsites.values()] as coWebsite, index (coWebsite.iframe.id)}
{#each [...$coWebsites.values()] as coWebsite, index (coWebsite.getId())}
<CoWebsiteThumbnail {index} {coWebsite} {vertical} />
{/each}
</div>

View File

@ -9,13 +9,11 @@
function closeCoWebsite() {
if ($highlightedEmbedScreen?.type === "cowebsite") {
if ($highlightedEmbedScreen.embed.closable) {
coWebsiteManager.closeCoWebsite($highlightedEmbedScreen.embed).catch(() => {
console.error("Error during co-website highlighted closing");
});
if ($highlightedEmbedScreen.embed.isClosable()) {
coWebsiteManager.closeCoWebsite($highlightedEmbedScreen.embed);
} else {
coWebsiteManager.unloadCoWebsite($highlightedEmbedScreen.embed).catch(() => {
console.error("Error during co-website highlighted unloading");
coWebsiteManager.unloadCoWebsite($highlightedEmbedScreen.embed).catch((err) => {
console.error("Cannot unload co-website", err);
});
}
}
@ -68,9 +66,9 @@
/>
{/key}
{:else if $highlightedEmbedScreen.type === "cowebsite"}
{#key $highlightedEmbedScreen.embed.iframe.id}
{#key $highlightedEmbedScreen.embed.getId()}
<div
id={"cowebsite-slot-" + $highlightedEmbedScreen.embed.iframe.id}
id={"cowebsite-slot-" + $highlightedEmbedScreen.embed.getId()}
class="highlighted-cowebsite nes-container is-rounded"
>
<div class="actions">

View File

@ -121,7 +121,7 @@ export class GameMap {
return [];
}
public getCollisionsGrid(): number[][] {
public getCollisionGrid(): number[][] {
const grid: number[][] = [];
for (let y = 0; y < this.map.height; y += 1) {
const row: number[] = [];
@ -333,6 +333,9 @@ export class GameMap {
private isCollidingAt(x: number, y: number): boolean {
for (const layer of this.phaserLayers) {
if (!layer.visible) {
continue;
}
if (layer.getTileAt(x, y)?.properties[GameMapProperties.COLLIDES]) {
return true;
}

View File

@ -1,7 +1,6 @@
import type { GameScene } from "./GameScene";
import type { GameMap } from "./GameMap";
import { scriptUtils } from "../../Api/ScriptUtils";
import type { CoWebsite } from "../../WebRtc/CoWebsiteManager";
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore";
import { localUserStore } from "../../Connexion/LocalUserStore";
@ -9,17 +8,19 @@ import { get } from "svelte/store";
import { ON_ACTION_TRIGGER_BUTTON, ON_ICON_TRIGGER_BUTTON } from "../../WebRtc/LayoutManager";
import type { ITiledMapLayer } from "../Map/ITiledMap";
import { GameMapProperties } from "./GameMapProperties";
enum OpenCoWebsiteState {
ASLEEP,
OPENED,
MUST_BE_CLOSE,
}
import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite";
import { SimpleCoWebsite } from "../../WebRtc/CoWebsite/SimpleCoWebsite";
import { jitsiFactory } from "../../WebRtc/JitsiFactory";
import { JITSI_PRIVATE_MODE, JITSI_URL } from "../../Enum/EnvironmentVariable";
import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite";
import { audioManagerFileStore, audioManagerVisibilityStore } from "../../Stores/AudioManagerStore";
import { iframeListener } from "../../Api/IframeListener";
import { Room } from "../../Connexion/Room";
import LL from "../../i18n/i18n-svelte";
interface OpenCoWebsite {
actionId: string;
coWebsite?: CoWebsite;
state: OpenCoWebsiteState;
}
export class GameMapPropertiesListener {
@ -29,6 +30,7 @@ export class GameMapPropertiesListener {
constructor(private scene: GameScene, private gameMap: GameMap) {}
register() {
// Website on new tab
this.gameMap.onPropertyChange(GameMapProperties.OPEN_TAB, (newValue, oldValue, allProps) => {
if (newValue === undefined) {
layoutManagerActionStore.removeAction("openTab");
@ -39,7 +41,7 @@ export class GameMapPropertiesListener {
if (forceTrigger || openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
let message = allProps.get(GameMapProperties.OPEN_WEBSITE_TRIGGER_MESSAGE);
if (message === undefined) {
message = "Press SPACE or touch here to open web site in new tab";
message = get(LL).trigger.newTab();
}
layoutManagerActionStore.addAction({
uuid: "openTab",
@ -54,6 +56,129 @@ export class GameMapPropertiesListener {
}
});
// Jitsi room
this.gameMap.onPropertyChange(GameMapProperties.JITSI_ROOM, (newValue, oldValue, allProps) => {
if (newValue === undefined) {
layoutManagerActionStore.removeAction("jitsi");
coWebsiteManager.getCoWebsites().forEach((coWebsite) => {
if (coWebsite instanceof JitsiCoWebsite) {
coWebsiteManager.closeCoWebsite(coWebsite);
}
});
} else {
const openJitsiRoomFunction = () => {
const roomName = jitsiFactory.getRoomName(newValue.toString(), this.scene.instance);
const jitsiUrl = allProps.get(GameMapProperties.JITSI_URL) as string | undefined;
if (JITSI_PRIVATE_MODE && !jitsiUrl) {
const adminTag = allProps.get(GameMapProperties.JITSI_ADMIN_ROOM_TAG) as string | undefined;
this.scene.connection?.emitQueryJitsiJwtMessage(roomName, adminTag);
} else {
let domain = jitsiUrl || JITSI_URL;
if (domain === undefined) {
throw new Error("Missing JITSI_URL environment variable or jitsiUrl parameter in the map.");
}
if (domain.substring(0, 7) !== "http://" && domain.substring(0, 8) !== "https://") {
domain = `${location.protocol}//${domain}`;
}
const coWebsite = new JitsiCoWebsite(new URL(domain), false, undefined, undefined, false);
coWebsiteManager.addCoWebsiteToStore(coWebsite, 0);
this.scene.initialiseJitsi(coWebsite, roomName, undefined);
}
layoutManagerActionStore.removeAction("jitsi");
};
const jitsiTriggerValue = allProps.get(GameMapProperties.JITSI_TRIGGER);
const forceTrigger = localUserStore.getForceCowebsiteTrigger();
if (forceTrigger || jitsiTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
let message = allProps.get(GameMapProperties.JITSI_TRIGGER_MESSAGE);
if (message === undefined) {
message = get(LL).trigger.jitsiRoom();
}
layoutManagerActionStore.addAction({
uuid: "jitsi",
type: "message",
message: message,
callback: () => openJitsiRoomFunction(),
userInputManager: this.scene.userInputManager,
});
} else {
openJitsiRoomFunction();
}
}
});
this.gameMap.onPropertyChange(GameMapProperties.EXIT_SCENE_URL, (newValue, oldValue) => {
if (newValue) {
this.scene
.onMapExit(
Room.getRoomPathFromExitSceneUrl(
newValue as string,
window.location.toString(),
this.scene.MapUrlFile
)
)
.catch((e) => console.error(e));
} else {
setTimeout(() => {
layoutManagerActionStore.removeAction("roomAccessDenied");
}, 2000);
}
});
this.gameMap.onPropertyChange(GameMapProperties.EXIT_URL, (newValue, oldValue) => {
if (newValue) {
this.scene
.onMapExit(Room.getRoomPathFromExitUrl(newValue as string, window.location.toString()))
.catch((e) => console.error(e));
} else {
setTimeout(() => {
layoutManagerActionStore.removeAction("roomAccessDenied");
}, 2000);
}
});
this.gameMap.onPropertyChange(GameMapProperties.SILENT, (newValue, oldValue) => {
if (newValue === undefined || newValue === false || newValue === "") {
this.scene.connection?.setSilent(false);
this.scene.CurrentPlayer.noSilent();
} else {
this.scene.connection?.setSilent(true);
this.scene.CurrentPlayer.isSilent();
}
});
this.gameMap.onPropertyChange(GameMapProperties.PLAY_AUDIO, (newValue, oldValue, allProps) => {
const volume = allProps.get(GameMapProperties.AUDIO_VOLUME) as number | undefined;
const loop = allProps.get(GameMapProperties.AUDIO_LOOP) as boolean | undefined;
newValue === undefined
? audioManagerFileStore.unloadAudio()
: audioManagerFileStore.playAudio(newValue, this.scene.getMapDirUrl(), volume, loop);
audioManagerVisibilityStore.set(!(newValue === undefined));
});
// TODO: This legacy property should be removed at some point
this.gameMap.onPropertyChange(GameMapProperties.PLAY_AUDIO_LOOP, (newValue, oldValue) => {
newValue === undefined
? audioManagerFileStore.unloadAudio()
: audioManagerFileStore.playAudio(newValue, this.scene.getMapDirUrl(), undefined, true);
audioManagerVisibilityStore.set(!(newValue === undefined));
});
// TODO: Legacy functionnality replace by layer change
this.gameMap.onPropertyChange(GameMapProperties.ZONE, (newValue, oldValue) => {
if (oldValue) {
iframeListener.sendLeaveEvent(oldValue as string);
}
if (newValue) {
iframeListener.sendEnterEvent(newValue as string);
}
});
// Open a new co-website by the property.
this.gameMap.onEnterLayer((newLayers) => {
const handler = () => {
@ -106,49 +231,33 @@ export class GameMapPropertiesListener {
return;
}
this.coWebsitesOpenByLayer.set(layer, {
const coWebsiteOpen: OpenCoWebsite = {
actionId: actionId,
coWebsite: undefined,
state: OpenCoWebsiteState.ASLEEP,
});
};
this.coWebsitesOpenByLayer.set(layer, coWebsiteOpen);
const loadCoWebsiteFunction = (coWebsite: CoWebsite) => {
coWebsiteManager
.loadCoWebsite(coWebsite)
.then((coWebsite) => {
const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer);
if (coWebsiteOpen && coWebsiteOpen.state === OpenCoWebsiteState.MUST_BE_CLOSE) {
coWebsiteManager.closeCoWebsite(coWebsite).catch(() => {
console.error("Error during a co-website closing");
});
this.coWebsitesOpenByLayer.delete(layer);
this.coWebsitesActionTriggerByLayer.delete(layer);
} else {
this.coWebsitesOpenByLayer.set(layer, {
actionId,
coWebsite,
state: OpenCoWebsiteState.OPENED,
});
}
})
.catch(() => {
console.error("Error during loading a co-website: " + coWebsite.url);
});
coWebsiteManager.loadCoWebsite(coWebsite).catch(() => {
console.error("Error during loading a co-website: " + coWebsite.getUrl());
});
layoutManagerActionStore.removeAction(actionId);
};
const openCoWebsiteFunction = () => {
const coWebsite = coWebsiteManager.addCoWebsite(
openWebsiteProperty ?? "",
this.scene.MapUrlFile,
const coWebsite = new SimpleCoWebsite(
new URL(openWebsiteProperty ?? "", this.scene.MapUrlFile),
allowApiProperty,
websitePolicyProperty,
websiteWidthProperty,
websitePositionProperty,
false
);
coWebsiteOpen.coWebsite = coWebsite;
coWebsiteManager.addCoWebsiteToStore(coWebsite, websitePositionProperty);
loadCoWebsiteFunction(coWebsite);
};
@ -157,7 +266,7 @@ export class GameMapPropertiesListener {
websiteTriggerProperty === ON_ACTION_TRIGGER_BUTTON
) {
if (!websiteTriggerMessageProperty) {
websiteTriggerMessageProperty = "Press SPACE or touch here to open web site";
websiteTriggerMessageProperty = get(LL).trigger.cowebsite();
}
this.coWebsitesActionTriggerByLayer.set(layer, actionId);
@ -170,21 +279,17 @@ export class GameMapPropertiesListener {
userInputManager: this.scene.userInputManager,
});
} else if (websiteTriggerProperty === ON_ICON_TRIGGER_BUTTON) {
const coWebsite = coWebsiteManager.addCoWebsite(
openWebsiteProperty,
this.scene.MapUrlFile,
const coWebsite = new SimpleCoWebsite(
new URL(openWebsiteProperty ?? "", this.scene.MapUrlFile),
allowApiProperty,
websitePolicyProperty,
websiteWidthProperty,
websitePositionProperty,
false
);
const ObjectByLayer = this.coWebsitesOpenByLayer.get(layer);
coWebsiteOpen.coWebsite = coWebsite;
if (ObjectByLayer) {
ObjectByLayer.coWebsite = coWebsite;
}
coWebsiteManager.addCoWebsiteToStore(coWebsite, websitePositionProperty);
}
if (!websiteTriggerProperty) {
@ -228,12 +333,10 @@ export class GameMapPropertiesListener {
return;
}
if (coWebsiteOpen.state === OpenCoWebsiteState.ASLEEP) {
coWebsiteOpen.state = OpenCoWebsiteState.MUST_BE_CLOSE;
}
const coWebsite = coWebsiteOpen.coWebsite;
if (coWebsiteOpen.coWebsite !== undefined) {
coWebsiteManager.closeCoWebsite(coWebsiteOpen.coWebsite).catch((e) => console.error(e));
if (coWebsite) {
coWebsiteManager.closeCoWebsite(coWebsite);
}
this.coWebsitesOpenByLayer.delete(layer);

View File

@ -5,7 +5,7 @@ import { get, Unsubscriber } from "svelte/store";
import { userMessageManager } from "../../Administration/UserMessageManager";
import { connectionManager } from "../../Connexion/ConnectionManager";
import { CoWebsite, coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
import { urlManager } from "../../Url/UrlManager";
import { mediaManager } from "../../WebRtc/MediaManager";
import { UserInputManager } from "../UserInput/UserInputManager";
@ -20,9 +20,8 @@ import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager";
import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager";
import { ON_ACTION_TRIGGER_BUTTON } from "../../WebRtc/LayoutManager";
import { iframeListener } from "../../Api/IframeListener";
import { DEBUG_MODE, JITSI_PRIVATE_MODE, MAX_PER_GROUP, POSITION_DELAY } from "../../Enum/EnvironmentVariable";
import { DEBUG_MODE, JITSI_URL, MAX_PER_GROUP, POSITION_DELAY } from "../../Enum/EnvironmentVariable";
import { ProtobufClientUtils } from "../../Network/ProtobufClientUtils";
import { Room } from "../../Connexion/Room";
import { jitsiFactory } from "../../WebRtc/JitsiFactory";
@ -76,7 +75,7 @@ import { emoteStore, emoteMenuStore } from "../../Stores/EmoteStore";
import { userIsAdminStore } from "../../Stores/GameStore";
import { contactPageStore } from "../../Stores/MenuStore";
import type { WasCameraUpdatedEvent } from "../../Api/Events/WasCameraUpdatedEvent";
import { audioManagerFileStore, audioManagerVisibilityStore } from "../../Stores/AudioManagerStore";
import { audioManagerFileStore } from "../../Stores/AudioManagerStore";
import EVENT_TYPE = Phaser.Scenes.Events;
import Texture = Phaser.Textures.Texture;
@ -94,6 +93,9 @@ import { GameSceneUserInputHandler } from "../UserInput/GameSceneUserInputHandle
import { locale } from "../../i18n/i18n-svelte";
import { StringUtils } from "../../Utils/StringUtils";
import { startLayerNamesStore } from "../../Stores/StartLayerNamesStore";
import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite";
import { SimpleCoWebsite } from "../../WebRtc/CoWebsite/SimpleCoWebsite";
import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite";
export interface GameSceneInitInterface {
initPosition: PointInterface | null;
reconnecting: boolean;
@ -564,7 +566,7 @@ export class GameScene extends DirtyScene {
this.pathfindingManager = new PathfindingManager(
this,
this.gameMap.getCollisionsGrid(),
this.gameMap.getCollisionGrid(),
this.gameMap.getTileDimensions()
);
@ -582,7 +584,7 @@ export class GameScene extends DirtyScene {
this.pathfindingManager = new PathfindingManager(
this,
this.gameMap.getCollisionsGrid(),
this.gameMap.getCollisionGrid(),
this.gameMap.getTileDimensions()
);
@ -633,7 +635,6 @@ export class GameScene extends DirtyScene {
);
new GameMapPropertiesListener(this, this.gameMap).register();
this.triggerOnMapLayerPropertyChange();
if (!this.room.isDisconnected()) {
this.scene.sleep();
@ -800,7 +801,19 @@ export class GameScene extends DirtyScene {
* Triggered when we receive the JWT token to connect to Jitsi
*/
this.connection.sendJitsiJwtMessageStream.subscribe((message) => {
this.startJitsi(message.jitsiRoom, message.jwt);
if (!JITSI_URL) {
throw new Error("Missing JITSI_URL environment variable.");
}
let domain = JITSI_URL;
if (domain.substring(0, 7) !== "http://" && domain.substring(0, 8) !== "https://") {
domain = `${location.protocol}//${domain}`;
}
const coWebsite = new JitsiCoWebsite(new URL(domain), false, undefined, undefined, false);
coWebsiteManager.addCoWebsiteToStore(coWebsite, 0);
this.initialiseJitsi(coWebsite, message.jitsiRoom, message.jwt);
});
this.messageSubscription = this.connection.worldFullMessageStream.subscribe((message) => {
@ -937,103 +950,6 @@ export class GameScene extends DirtyScene {
}
}
private triggerOnMapLayerPropertyChange() {
this.gameMap.onPropertyChange(GameMapProperties.EXIT_SCENE_URL, (newValue, oldValue) => {
if (newValue) {
this.onMapExit(
Room.getRoomPathFromExitSceneUrl(newValue as string, window.location.toString(), this.MapUrlFile)
).catch((e) => console.error(e));
} else {
setTimeout(() => {
layoutManagerActionStore.removeAction("roomAccessDenied");
}, 2000);
}
});
this.gameMap.onPropertyChange(GameMapProperties.EXIT_URL, (newValue, oldValue) => {
if (newValue) {
this.onMapExit(Room.getRoomPathFromExitUrl(newValue as string, window.location.toString())).catch((e) =>
console.error(e)
);
} else {
setTimeout(() => {
layoutManagerActionStore.removeAction("roomAccessDenied");
}, 2000);
}
});
this.gameMap.onPropertyChange(GameMapProperties.JITSI_ROOM, (newValue, oldValue, allProps) => {
if (newValue === undefined) {
layoutManagerActionStore.removeAction("jitsi");
this.stopJitsi();
} else {
const openJitsiRoomFunction = () => {
const roomName = jitsiFactory.getRoomName(newValue.toString(), this.instance);
const jitsiUrl = allProps.get(GameMapProperties.JITSI_URL) as string | undefined;
if (JITSI_PRIVATE_MODE && !jitsiUrl) {
const adminTag = allProps.get(GameMapProperties.JITSI_ADMIN_ROOM_TAG) as string | undefined;
this.connection?.emitQueryJitsiJwtMessage(roomName, adminTag);
} else {
this.startJitsi(roomName, undefined);
}
layoutManagerActionStore.removeAction("jitsi");
};
const jitsiTriggerValue = allProps.get(GameMapProperties.JITSI_TRIGGER);
const forceTrigger = localUserStore.getForceCowebsiteTrigger();
if (forceTrigger || jitsiTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
let message = allProps.get(GameMapProperties.JITSI_TRIGGER_MESSAGE);
if (message === undefined) {
message = "Press SPACE or touch here to enter Jitsi Meet room";
}
layoutManagerActionStore.addAction({
uuid: "jitsi",
type: "message",
message: message,
callback: () => openJitsiRoomFunction(),
userInputManager: this.userInputManager,
});
} else {
openJitsiRoomFunction();
}
}
});
this.gameMap.onPropertyChange(GameMapProperties.SILENT, (newValue, oldValue) => {
if (newValue === undefined || newValue === false || newValue === "") {
this.connection?.setSilent(false);
this.CurrentPlayer.noSilent();
} else {
this.connection?.setSilent(true);
this.CurrentPlayer.isSilent();
}
});
this.gameMap.onPropertyChange(GameMapProperties.PLAY_AUDIO, (newValue, oldValue, allProps) => {
const volume = allProps.get(GameMapProperties.AUDIO_VOLUME) as number | undefined;
const loop = allProps.get(GameMapProperties.AUDIO_LOOP) as boolean | undefined;
newValue === undefined
? audioManagerFileStore.unloadAudio()
: audioManagerFileStore.playAudio(newValue, this.getMapDirUrl(), volume, loop);
audioManagerVisibilityStore.set(!(newValue === undefined));
});
// TODO: This legacy property should be removed at some point
this.gameMap.onPropertyChange(GameMapProperties.PLAY_AUDIO_LOOP, (newValue, oldValue) => {
newValue === undefined
? audioManagerFileStore.unloadAudio()
: audioManagerFileStore.playAudio(newValue, this.getMapDirUrl(), undefined, true);
audioManagerVisibilityStore.set(!(newValue === undefined));
});
// TODO: Legacy functionnality replace by layer change
this.gameMap.onPropertyChange(GameMapProperties.ZONE, (newValue, oldValue) => {
if (oldValue) {
iframeListener.sendLeaveEvent(oldValue as string);
}
if (newValue) {
iframeListener.sendEnterEvent(newValue as string);
}
});
}
private listenToIframeEvents(): void {
this.iframeSubscriptionList = [];
this.iframeSubscriptionList.push(
@ -1266,13 +1182,11 @@ ${escapedMessage}
throw new Error("Unknown query source");
}
const coWebsite = coWebsiteManager.addCoWebsite(
openCoWebsite.url,
iframeListener.getBaseUrlFromSource(source),
const coWebsite: SimpleCoWebsite = new SimpleCoWebsite(
new URL(openCoWebsite.url, iframeListener.getBaseUrlFromSource(source)),
openCoWebsite.allowApi,
openCoWebsite.allowPolicy,
openCoWebsite.widthPercent,
openCoWebsite.position,
openCoWebsite.closable ?? true
);
@ -1281,7 +1195,7 @@ ${escapedMessage}
}
return {
id: coWebsite.iframe.id,
id: coWebsite.getId(),
};
});
@ -1290,27 +1204,23 @@ ${escapedMessage}
return coWebsites.map((coWebsite: CoWebsite) => {
return {
id: coWebsite.iframe.id,
id: coWebsite.getId(),
};
});
});
iframeListener.registerAnswerer("closeCoWebsite", async (coWebsiteId) => {
iframeListener.registerAnswerer("closeCoWebsite", (coWebsiteId) => {
const coWebsite = coWebsiteManager.getCoWebsiteById(coWebsiteId);
if (!coWebsite) {
throw new Error("Unknown co-website");
}
return coWebsiteManager.closeCoWebsite(coWebsite).catch((error) => {
throw new Error("Error on closing co-website");
});
return coWebsiteManager.closeCoWebsite(coWebsite);
});
iframeListener.registerAnswerer("closeCoWebsites", async () => {
return await coWebsiteManager.closeCoWebsites().catch((error) => {
throw new Error("Error on closing all co-websites");
});
iframeListener.registerAnswerer("closeCoWebsites", () => {
return coWebsiteManager.closeCoWebsites();
});
iframeListener.registerAnswerer("getMapData", () => {
@ -1395,7 +1305,7 @@ ${escapedMessage}
//Create new colliders with the new GameMap
this.createCollisionWithPlayer();
//Create new trigger with the new GameMap
this.triggerOnMapLayerPropertyChange();
new GameMapPropertiesListener(this, this.gameMap).register();
resolve(newFirstgid);
});
});
@ -1508,14 +1418,15 @@ ${escapedMessage}
phaserLayers[i].setCollisionByProperty({ collides: true }, visible);
}
}
this.pathfindingManager.setCollisionGrid(this.gameMap.getCollisionGrid());
this.markDirty();
}
private getMapDirUrl(): string {
return this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf("/"));
public getMapDirUrl(): string {
return this.MapUrlFile.substring(0, this.MapUrlFile.lastIndexOf("/"));
}
private async onMapExit(roomUrl: URL) {
public async onMapExit(roomUrl: URL) {
if (this.mapTransitioning) return;
this.mapTransitioning = true;
@ -1574,14 +1485,13 @@ ${escapedMessage}
public cleanupClosingScene(): void {
// stop playing audio, close any open website, stop any open Jitsi
coWebsiteManager.closeCoWebsites().catch((e) => console.error(e));
coWebsiteManager.closeCoWebsites();
// Stop the script, if any
const scripts = this.getScriptUrls(this.mapFile);
for (const script of scripts) {
iframeListener.unregisterScript(script);
}
this.stopJitsi();
audioManagerFileStore.unloadAudio();
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
this.connection?.closeConnection();
@ -2136,7 +2046,7 @@ ${escapedMessage}
mediaManager.hideMyCamera();
}
public startJitsi(roomName: string, jwt?: string): void {
public initialiseJitsi(coWebsite: JitsiCoWebsite, roomName: string, jwt?: string): void {
const allProps = this.gameMap.getCurrentProperties();
const jitsiConfig = this.safeParseJSONstring(
allProps.get(GameMapProperties.JITSI_CONFIG) as string | undefined,
@ -2148,20 +2058,15 @@ ${escapedMessage}
);
const jitsiUrl = allProps.get(GameMapProperties.JITSI_URL) as string | undefined;
jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl).catch(() => {
console.error("Cannot start a Jitsi co-website");
coWebsite.setJitsiLoadPromise(() => {
return jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl);
});
this.disableMediaBehaviors();
analyticsClient.enteredJitsi(roomName, this.room.id);
}
public stopJitsi(): void {
const coWebsite = coWebsiteManager.searchJitsi();
if (coWebsite) {
coWebsiteManager.closeCoWebsite(coWebsite).catch((e) => {
console.error("Error during Jitsi co-website closing", e);
});
}
coWebsiteManager.loadCoWebsite(coWebsite).catch((err) => {
console.error(err);
});
analyticsClient.enteredJitsi(roomName, this.room.id);
}
//todo: put this into an 'orchestrator' scene (EntryScene?)

View File

@ -1,5 +1,5 @@
import { derived, get, writable } from "svelte/store";
import type { CoWebsite } from "../WebRtc/CoWebsiteManager";
import { derived, writable } from "svelte/store";
import type { CoWebsite } from "../WebRtc/CoWebsite/CoWesbite";
function createCoWebsiteStore() {
const { subscribe, set, update } = writable(Array<CoWebsite>());
@ -9,7 +9,7 @@ function createCoWebsiteStore() {
return {
subscribe,
add: (coWebsite: CoWebsite, position?: number) => {
coWebsite.state.subscribe((value) => {
coWebsite.getStateSubscriber().subscribe((value) => {
update((currentArray) => currentArray);
});
@ -31,7 +31,7 @@ function createCoWebsiteStore() {
},
remove: (coWebsite: CoWebsite) => {
update((currentArray) => [
...currentArray.filter((currentCoWebsite) => currentCoWebsite.iframe.id !== coWebsite.iframe.id),
...currentArray.filter((currentCoWebsite) => currentCoWebsite.getId() !== coWebsite.getId()),
]);
},
empty: () => {
@ -43,9 +43,9 @@ function createCoWebsiteStore() {
export const coWebsites = createCoWebsiteStore();
export const coWebsitesNotAsleep = derived([coWebsites], ([$coWebsites]) =>
$coWebsites.filter((coWebsite) => get(coWebsite.state) !== "asleep")
$coWebsites.filter((coWebsite) => coWebsite.getState() !== "asleep")
);
export const mainCoWebsite = derived([coWebsites], ([$coWebsites]) =>
$coWebsites.find((coWebsite) => get(coWebsite.state) !== "asleep")
$coWebsites.find((coWebsite) => coWebsite.getState() !== "asleep")
);

View File

@ -1,5 +1,5 @@
import { derived, get, writable } from "svelte/store";
import type { CoWebsite } from "../WebRtc/CoWebsiteManager";
import type { CoWebsite } from "../WebRtc/CoWebsite/CoWesbite";
import { LayoutMode } from "../WebRtc/LayoutManager";
import { coWebsites } from "./CoWebsiteStore";
import { Streamable, streamableCollectionStore } from "./StreamableCollectionStore";
@ -31,7 +31,7 @@ function createHighlightedEmbedScreenStore() {
embedScreen.type !== currentEmbedScreen.type ||
(embedScreen.type === "cowebsite" &&
currentEmbedScreen.type === "cowebsite" &&
embedScreen.embed.iframe.id !== currentEmbedScreen.embed.iframe.id) ||
embedScreen.embed.getId() !== currentEmbedScreen.embed.getId()) ||
(embedScreen.type === "streamable" &&
currentEmbedScreen.type === "streamable" &&
embedScreen.embed.uniqueId !== currentEmbedScreen.embed.uniqueId)

View File

@ -19,6 +19,10 @@ export class PathfindingManager {
this.setEasyStarGrid(collisionsGrid);
}
public setCollisionGrid(collisionGrid: number[][]): void {
this.setEasyStarGrid(collisionGrid);
}
public async findPath(
start: { x: number; y: number },
end: { x: number; y: number },

View File

@ -0,0 +1,17 @@
import type CancelablePromise from "cancelable-promise";
import type { Readable, Writable } from "svelte/store";
export type CoWebsiteState = "asleep" | "loading" | "ready";
export interface CoWebsite {
getId(): string;
getUrl(): URL;
getState(): CoWebsiteState;
getStateSubscriber(): Readable<CoWebsiteState>;
getIframe(): HTMLIFrameElement | undefined;
getLoadIframe(): CancelablePromise<HTMLIFrameElement> | undefined;
getWidthPercent(): number | undefined;
isClosable(): boolean;
load(): CancelablePromise<HTMLIFrameElement>;
unload(): Promise<void>;
}

View File

@ -0,0 +1,49 @@
import CancelablePromise from "cancelable-promise";
import { gameManager } from "../../Phaser/Game/GameManager";
import { jitsiFactory } from "../JitsiFactory";
import { SimpleCoWebsite } from "./SimpleCoWebsite";
export class JitsiCoWebsite extends SimpleCoWebsite {
private jitsiLoadPromise?: () => CancelablePromise<HTMLIFrameElement>;
setJitsiLoadPromise(promise: () => CancelablePromise<HTMLIFrameElement>): void {
this.jitsiLoadPromise = promise;
}
load(): CancelablePromise<HTMLIFrameElement> {
return new CancelablePromise((resolve, reject, cancel) => {
this.state.set("loading");
gameManager.getCurrentGameScene().disableMediaBehaviors();
if (!this.jitsiLoadPromise) {
return reject("Undefined Jitsi start callback");
}
const jitsiLoading = this.jitsiLoadPromise()
.then((iframe) => {
this.iframe = iframe;
this.iframe.classList.add("pixel");
this.state.set("ready");
return resolve(iframe);
})
.catch((err) => {
return reject(err);
});
cancel(() => {
jitsiLoading.cancel();
this.unload().catch((err) => {
console.error("Cannot unload Jitsi co-website while cancel loading", err);
});
});
});
}
unload(): Promise<void> {
jitsiFactory.destroy();
gameManager.getCurrentGameScene().enableMediaBehaviors();
return super.unload();
}
}

View File

@ -0,0 +1,133 @@
import CancelablePromise from "cancelable-promise";
import { get, Readable, writable, Writable } from "svelte/store";
import { iframeListener } from "../../Api/IframeListener";
import { coWebsiteManager } from "../CoWebsiteManager";
import type { CoWebsite, CoWebsiteState } from "./CoWesbite";
export class SimpleCoWebsite implements CoWebsite {
protected id: string;
protected url: URL;
protected state: Writable<CoWebsiteState>;
protected iframe?: HTMLIFrameElement;
protected loadIframe?: CancelablePromise<HTMLIFrameElement>;
protected allowApi?: boolean;
protected allowPolicy?: string;
protected widthPercent?: number;
protected closable: boolean;
constructor(url: URL, allowApi?: boolean, allowPolicy?: string, widthPercent?: number, closable?: boolean) {
this.id = coWebsiteManager.generateUniqueId();
this.url = url;
this.state = writable("asleep" as CoWebsiteState);
this.allowApi = allowApi;
this.allowPolicy = allowPolicy;
this.widthPercent = widthPercent;
this.closable = closable ?? false;
}
getId(): string {
return this.id;
}
getUrl(): URL {
return this.url;
}
getState(): CoWebsiteState {
return get(this.state);
}
getStateSubscriber(): Readable<CoWebsiteState> {
return this.state;
}
getIframe(): HTMLIFrameElement | undefined {
return this.iframe;
}
getLoadIframe(): CancelablePromise<HTMLIFrameElement> | undefined {
return this.loadIframe;
}
getWidthPercent(): number | undefined {
return this.widthPercent;
}
isClosable(): boolean {
return this.closable;
}
load(): CancelablePromise<HTMLIFrameElement> {
this.loadIframe = new CancelablePromise((resolve, reject, cancel) => {
this.state.set("loading");
const iframe = document.createElement("iframe");
this.iframe = iframe;
this.iframe.src = this.url.toString();
this.iframe.id = this.id;
if (this.allowPolicy) {
this.iframe.allow = this.allowPolicy;
}
if (this.allowApi) {
iframeListener.registerIframe(this.iframe);
}
this.iframe.classList.add("pixel");
const onloadPromise = new Promise<void>((resolve) => {
if (this.iframe) {
this.iframe.onload = () => {
this.state.set("ready");
resolve();
};
}
});
const onTimeoutPromise = new Promise<void>((resolve) => {
setTimeout(() => resolve(), 2000);
});
coWebsiteManager.getCoWebsiteBuffer().appendChild(this.iframe);
const race = CancelablePromise.race([onloadPromise, onTimeoutPromise])
.then(() => {
return resolve(iframe);
})
.catch((err) => {
console.error("Error on co-website loading => ", err);
return