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

This commit is contained in:
_Bastler 2021-09-02 19:18:25 +02:00
commit 62e164de9e
8 changed files with 232 additions and 128 deletions

View File

@ -40,7 +40,7 @@
}, },
"homepage": "https://github.com/thecodingmachine/workadventure#readme", "homepage": "https://github.com/thecodingmachine/workadventure#readme",
"dependencies": { "dependencies": {
"@workadventure/tiled-map-type-guard": "^1.0.0", "@workadventure/tiled-map-type-guard": "^1.0.2",
"axios": "^0.21.1", "axios": "^0.21.1",
"busboy": "^0.3.1", "busboy": "^0.3.1",
"circular-json": "^0.5.9", "circular-json": "^0.5.9",

View File

@ -1,7 +1,12 @@
/** /**
* Handles variables shared between the scripting API and the server. * Handles variables shared between the scripting API and the server.
*/ */
import { ITiledMap, ITiledMapObject, ITiledMapObjectLayer } from "@workadventure/tiled-map-type-guard/dist"; import {
ITiledMap,
ITiledMapLayer,
ITiledMapObject,
ITiledMapObjectLayer,
} from "@workadventure/tiled-map-type-guard/dist";
import { User } from "_Model/User"; import { User } from "_Model/User";
import { variablesRepository } from "./Repository/VariablesRepository"; import { variablesRepository } from "./Repository/VariablesRepository";
import { redisClient } from "./RedisClient"; import { redisClient } from "./RedisClient";
@ -83,25 +88,33 @@ export class VariablesManager {
private static findVariablesInMap(map: ITiledMap): Map<string, Variable> { private static findVariablesInMap(map: ITiledMap): Map<string, Variable> {
const objects = new Map<string, Variable>(); const objects = new Map<string, Variable>();
for (const layer of map.layers) { for (const layer of map.layers) {
if (layer.type === "objectgroup") { this.recursiveFindVariablesInLayer(layer, objects);
for (const object of (layer as ITiledMapObjectLayer).objects) {
if (object.type === "variable") {
if (object.template) {
console.warn(
'Warning, a variable object is using a Tiled "template". WorkAdventure does not support objects generated from Tiled templates.'
);
continue;
}
// We store a copy of the object (to make it immutable)
objects.set(object.name, this.iTiledObjectToVariable(object));
}
}
}
} }
return objects; return objects;
} }
private static recursiveFindVariablesInLayer(layer: ITiledMapLayer, objects: Map<string, Variable>): void {
if (layer.type === "objectgroup") {
for (const object of layer.objects) {
if (object.type === "variable") {
if (object.template) {
console.warn(
'Warning, a variable object is using a Tiled "template". WorkAdventure does not support objects generated from Tiled templates.'
);
continue;
}
// We store a copy of the object (to make it immutable)
objects.set(object.name, this.iTiledObjectToVariable(object));
}
}
} else if (layer.type === "group") {
for (const innerLayer of layer.layers) {
this.recursiveFindVariablesInLayer(innerLayer, objects);
}
}
}
private static iTiledObjectToVariable(object: ITiledMapObject): Variable { private static iTiledObjectToVariable(object: ITiledMapObject): Variable {
const variable: Variable = {}; const variable: Variable = {};

View File

@ -194,10 +194,10 @@
semver "^7.3.2" semver "^7.3.2"
tsutils "^3.17.1" tsutils "^3.17.1"
"@workadventure/tiled-map-type-guard@^1.0.0": "@workadventure/tiled-map-type-guard@^1.0.2":
version "1.0.0" version "1.0.2"
resolved "https://registry.yarnpkg.com/@workadventure/tiled-map-type-guard/-/tiled-map-type-guard-1.0.0.tgz#02524602ee8b2688429a1f56df1d04da3fc171ba" resolved "https://registry.yarnpkg.com/@workadventure/tiled-map-type-guard/-/tiled-map-type-guard-1.0.2.tgz#4171550f6cd71be19791faef48360d65d698bcb0"
integrity sha512-Mc0SE128otQnYlScQWVaQVyu1+CkailU/FTBh09UTrVnBAhyMO+jIn9vT9+Dv244xq+uzgQDpXmiVdjgrYFQ+A== integrity sha512-RCtygGV5y9cb7QoyGMINBE9arM5pyXjkxvXgA5uXEv4GDbXKorhFim/rHgwbVR+eFnVF3rDgWbRnk3DIaHt+lQ==
dependencies: dependencies:
generic-type-guard "^3.4.1" generic-type-guard "^3.4.1"

View File

@ -0,0 +1,55 @@
import type { GameScene } from "./GameScene";
import type { GameMap } from "./GameMap";
import { scriptUtils } from "../../Api/ScriptUtils";
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore";
import {
ON_ACTION_TRIGGER_BUTTON,
TRIGGER_WEBSITE_PROPERTIES,
WEBSITE_MESSAGE_PROPERTIES,
} from "../../WebRtc/LayoutManager";
export class GameMapPropertiesListener {
constructor(private scene: GameScene, private gameMap: GameMap) {}
register() {
this.gameMap.onPropertyChange("openTab", (newValue) => {
if (typeof newValue == "string" && newValue.length) {
scriptUtils.openTab(newValue);
}
});
this.gameMap.onPropertyChange("openWebsite", (newValue, oldValue, allProps) => {
if (newValue === undefined) {
layoutManagerActionStore.removeAction("openWebsite");
coWebsiteManager.closeCoWebsite();
} else {
const openWebsiteFunction = () => {
coWebsiteManager.loadCoWebsite(
newValue as string,
this.scene.MapUrlFile,
allProps.get("openWebsiteAllowApi") as boolean | undefined,
allProps.get("openWebsitePolicy") as string | undefined,
allProps.get("openWebsiteWidth") as number | undefined
);
layoutManagerActionStore.removeAction("openWebsite");
};
const openWebsiteTriggerValue = allProps.get(TRIGGER_WEBSITE_PROPERTIES);
if (openWebsiteTriggerValue && openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
let message = allProps.get(WEBSITE_MESSAGE_PROPERTIES);
if (message === undefined) {
message = "Press SPACE or touch here to open web site";
}
layoutManagerActionStore.addAction({
uuid: "openWebsite",
type: "message",
message: message,
callback: () => openWebsiteFunction(),
userInputManager: this.scene.userInputManager,
});
} else {
openWebsiteFunction();
}
}
});
}
}

View File

@ -92,6 +92,7 @@ import Tileset = Phaser.Tilemaps.Tileset;
import { userIsAdminStore } from "../../Stores/GameStore"; import { userIsAdminStore } from "../../Stores/GameStore";
import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore"; import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore";
import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager"; import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager";
import { GameMapPropertiesListener } from "./GameMapPropertiesListener";
export interface GameSceneInitInterface { export interface GameSceneInitInterface {
initPosition: PointInterface | null; initPosition: PointInterface | null;
@ -580,6 +581,7 @@ export class GameScene extends DirtyScene {
this.updateCameraOffset(box) this.updateCameraOffset(box)
); );
new GameMapPropertiesListener(this, this.gameMap).register();
this.triggerOnMapLayerPropertyChange(); this.triggerOnMapLayerPropertyChange();
if (!this.room.isDisconnected()) { if (!this.room.isDisconnected()) {
@ -825,40 +827,7 @@ export class GameScene extends DirtyScene {
}, 2000); }, 2000);
} }
}); });
this.gameMap.onPropertyChange("openWebsite", (newValue, oldValue, allProps) => {
if (newValue === undefined) {
layoutManagerActionStore.removeAction("openWebsite");
coWebsiteManager.closeCoWebsite();
} else {
const openWebsiteFunction = () => {
coWebsiteManager.loadCoWebsite(
newValue as string,
this.MapUrlFile,
allProps.get("openWebsiteAllowApi") as boolean | undefined,
allProps.get("openWebsitePolicy") as string | undefined,
allProps.get("openWebsiteWidth") as number | undefined
);
layoutManagerActionStore.removeAction("openWebsite");
};
const openWebsiteTriggerValue = allProps.get(TRIGGER_WEBSITE_PROPERTIES);
if (openWebsiteTriggerValue && openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
let message = allProps.get(WEBSITE_MESSAGE_PROPERTIES);
if (message === undefined) {
message = "Press SPACE or touch here to open web site";
}
layoutManagerActionStore.addAction({
uuid: "openWebsite",
type: "message",
message: message,
callback: () => openWebsiteFunction(),
userInputManager: this.userInputManager,
});
} else {
openWebsiteFunction();
}
}
});
this.gameMap.onPropertyChange("jitsiRoom", (newValue, oldValue, allProps) => { this.gameMap.onPropertyChange("jitsiRoom", (newValue, oldValue, allProps) => {
if (newValue === undefined) { if (newValue === undefined) {
layoutManagerActionStore.removeAction("jitsi"); layoutManagerActionStore.removeAction("jitsi");

View File

@ -1,10 +1,7 @@
import type { RoomConnection } from "../../Connexion/RoomConnection"; import type { RoomConnection } from "../../Connexion/RoomConnection";
import { iframeListener } from "../../Api/IframeListener"; import { iframeListener } from "../../Api/IframeListener";
import type { Subscription } from "rxjs";
import type { GameMap } from "./GameMap"; import type { GameMap } from "./GameMap";
import type { ITile, ITiledMapObject } from "../Map/ITiledMap"; import type {ITiledMapLayer, ITiledMapObject, ITiledMapObjectLayer} from "../Map/ITiledMap";
import type { Var } from "svelte/types/compiler/interfaces";
import { init } from "svelte/internal";
interface Variable { interface Variable {
defaultValue: unknown; defaultValue: unknown;
@ -100,24 +97,33 @@ export class SharedVariablesManager {
private static findVariablesInMap(gameMap: GameMap): Map<string, Variable> { private static findVariablesInMap(gameMap: GameMap): Map<string, Variable> {
const objects = new Map<string, Variable>(); const objects = new Map<string, Variable>();
for (const layer of gameMap.getMap().layers) { for (const layer of gameMap.getMap().layers) {
if (layer.type === "objectgroup") { this.recursiveFindVariablesInLayer(layer, objects);
for (const object of layer.objects) {
if (object.type === "variable") {
if (object.template) {
console.warn(
'Warning, a variable object is using a Tiled "template". WorkAdventure does not support objects generated from Tiled templates.'
);
}
// We store a copy of the object (to make it immutable)
objects.set(object.name, this.iTiledObjectToVariable(object));
}
}
}
} }
return objects; return objects;
} }
private static recursiveFindVariablesInLayer(layer: ITiledMapLayer, objects: Map<string, Variable>): void {
if (layer.type === "objectgroup") {
for (const object of layer.objects) {
if (object.type === "variable") {
if (object.template) {
console.warn(
'Warning, a variable object is using a Tiled "template". WorkAdventure does not support objects generated from Tiled templates.'
);
continue;
}
// We store a copy of the object (to make it immutable)
objects.set(object.name, this.iTiledObjectToVariable(object));
}
}
} else if (layer.type === "group") {
for (const innerLayer of layer.layers) {
this.recursiveFindVariablesInLayer(innerLayer, objects);
}
}
}
private static iTiledObjectToVariable(object: ITiledMapObject): Variable { private static iTiledObjectToVariable(object: ITiledMapObject): Variable {
const variable: Variable = { const variable: Variable = {
defaultValue: undefined, defaultValue: undefined,

View File

@ -168,7 +168,13 @@ class CoWebsiteManager {
return iframe; return iframe;
} }
public loadCoWebsite(url: string, base: string, allowApi?: boolean, allowPolicy?: string, widthPercent?: number): void { public loadCoWebsite(
url: string,
base: string,
allowApi?: boolean,
allowPolicy?: string,
widthPercent?: number
): void {
this.load(); this.load();
this.cowebsiteMainDom.innerHTML = ``; this.cowebsiteMainDom.innerHTML = ``;

View File

@ -1,37 +1,67 @@
import {JITSI_URL} from "../Enum/EnvironmentVariable"; import { JITSI_URL } from "../Enum/EnvironmentVariable";
import {mediaManager} from "./MediaManager"; import { coWebsiteManager } from "./CoWebsiteManager";
import {coWebsiteManager} from "./CoWebsiteManager"; import { requestedCameraState, requestedMicrophoneState } from "../Stores/MediaStore";
import {requestedCameraState, requestedMicrophoneState} from "../Stores/MediaStore"; import { get } from "svelte/store";
import {get} from "svelte/store";
declare const window:any; // eslint-disable-line @typescript-eslint/no-explicit-any
interface jitsiConfigInterface { interface jitsiConfigInterface {
startWithAudioMuted: boolean startWithAudioMuted: boolean;
startWithVideoMuted: boolean startWithVideoMuted: boolean;
prejoinPageEnabled: boolean prejoinPageEnabled: boolean;
} }
const getDefaultConfig = () : jitsiConfigInterface => { interface JitsiOptions {
return { jwt?: string;
startWithAudioMuted: !get(requestedMicrophoneState), roomName: string;
startWithVideoMuted: !get(requestedCameraState), width: string;
prejoinPageEnabled: false height: string;
parentNode: HTMLElement;
configOverwrite: jitsiConfigInterface;
interfaceConfigOverwrite: typeof defaultInterfaceConfig;
onload?: Function;
}
interface JitsiApi {
executeCommand: (command: string, ...args: Array<unknown>) => void;
addListener: (type: string, callback: Function) => void;
removeListener: (type: string, callback: Function) => void;
dispose: () => void;
}
declare global {
interface Window {
JitsiMeetExternalAPI: new (domain: string, options: JitsiOptions) => JitsiApi;
} }
} }
const getDefaultConfig = (): jitsiConfigInterface => {
return {
startWithAudioMuted: !get(requestedMicrophoneState),
startWithVideoMuted: !get(requestedCameraState),
prejoinPageEnabled: false,
};
};
const mergeConfig = (config?: object) => { const mergeConfig = (config?: object) => {
const currentDefaultConfig = getDefaultConfig(); const currentDefaultConfig = getDefaultConfig();
if(!config){ if (!config) {
return currentDefaultConfig; return currentDefaultConfig;
} }
return { return {
...currentDefaultConfig, ...currentDefaultConfig,
...config, ...config,
startWithAudioMuted: (config as jitsiConfigInterface).startWithAudioMuted ? true : currentDefaultConfig.startWithAudioMuted, startWithAudioMuted: (config as jitsiConfigInterface).startWithAudioMuted
startWithVideoMuted: (config as jitsiConfigInterface).startWithVideoMuted ? true : currentDefaultConfig.startWithVideoMuted, ? true
prejoinPageEnabled: (config as jitsiConfigInterface).prejoinPageEnabled ? true : currentDefaultConfig.prejoinPageEnabled : currentDefaultConfig.startWithAudioMuted,
} startWithVideoMuted: (config as jitsiConfigInterface).startWithVideoMuted
} ? true
: currentDefaultConfig.startWithVideoMuted,
prejoinPageEnabled: (config as jitsiConfigInterface).prejoinPageEnabled
? true
: currentDefaultConfig.prejoinPageEnabled,
};
};
const defaultInterfaceConfig = { const defaultInterfaceConfig = {
SHOW_CHROME_EXTENSION_BANNER: false, SHOW_CHROME_EXTENSION_BANNER: false,
@ -49,28 +79,48 @@ const defaultInterfaceConfig = {
SHOW_WATERMARK_FOR_GUESTS: false, SHOW_WATERMARK_FOR_GUESTS: false,
TOOLBAR_BUTTONS: [ TOOLBAR_BUTTONS: [
'microphone', 'camera', 'closedcaptions', 'desktop', /*'embedmeeting',*/ 'fullscreen', "microphone",
'fodeviceselection', 'hangup', 'profile', 'chat', 'recording', "camera",
'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand', "closedcaptions",
'videoquality', 'filmstrip', /*'invite',*/ 'feedback', 'stats', 'shortcuts', "desktop",
'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone', /*'security'*/ /*'embedmeeting',*/ "fullscreen",
"fodeviceselection",
"hangup",
"profile",
"chat",
"recording",
"livestreaming",
"etherpad",
"sharedvideo",
"settings",
"raisehand",
"videoquality",
"filmstrip",
/*'invite',*/ "feedback",
"stats",
"shortcuts",
"tileview",
"videobackgroundblur",
"download",
"help",
"mute-everyone" /*'security'*/,
], ],
}; };
const slugify = (...args: (string | number)[]): string => { const slugify = (...args: (string | number)[]): string => {
const value = args.join(' ') const value = args.join(" ");
return value return value
.normalize('NFD') // split an accented letter in the base letter and the accent .normalize("NFD") // split an accented letter in the base letter and the accent
.replace(/[\u0300-\u036f]/g, '') // remove all previously split accents .replace(/[\u0300-\u036f]/g, "") // remove all previously split accents
.toLowerCase() .toLowerCase()
.trim() .trim()
.replace(/[^a-z0-9 ]/g, '') // remove all chars not letters, numbers and spaces (to be replaced) .replace(/[^a-z0-9 ]/g, "") // remove all chars not letters, numbers and spaces (to be replaced)
.replace(/\s+/g, '-') // separator .replace(/\s+/g, "-"); // separator
} };
class JitsiFactory { class JitsiFactory {
private jitsiApi: any; // eslint-disable-line @typescript-eslint/no-explicit-any private jitsiApi?: JitsiApi;
private audioCallback = this.onAudioChange.bind(this); private audioCallback = this.onAudioChange.bind(this);
private videoCallback = this.onVideoChange.bind(this); private videoCallback = this.onVideoChange.bind(this);
private jitsiScriptLoaded: boolean = false; private jitsiScriptLoaded: boolean = false;
@ -79,11 +129,19 @@ class JitsiFactory {
* Slugifies the room name and prepends the room name with the instance * Slugifies the room name and prepends the room name with the instance
*/ */
public getRoomName(roomName: string, instance: string): string { public getRoomName(roomName: string, instance: string): string {
return slugify(instance.replace('/', '-') + "-" + roomName); return slugify(instance.replace("/", "-") + "-" + roomName);
} }
public start(roomName: string, playerName:string, jwt?: string, config?: object, interfaceConfig?: object, jitsiUrl?: string, jitsiWidth?: number): void { public start(
coWebsiteManager.insertCoWebsite((async cowebsiteDiv => { roomName: string,
playerName: string,
jwt?: string,
config?: object,
interfaceConfig?: object,
jitsiUrl?: string,
jitsiWidth?: number
): void {
coWebsiteManager.insertCoWebsite(async (cowebsiteDiv) => {
// Jitsi meet external API maintains some data in local storage // Jitsi meet external API maintains some data in local storage
// which is sent via the appData URL parameter when joining a // which is sent via the appData URL parameter when joining a
// conference. Problem is that this data grows indefinitely. Thus // conference. Problem is that this data grows indefinitely. Thus
@ -94,18 +152,18 @@ class JitsiFactory {
const domain = jitsiUrl || JITSI_URL; const domain = jitsiUrl || JITSI_URL;
if (domain === undefined) { if (domain === undefined) {
throw new Error('Missing JITSI_URL environment variable or jitsiUrl parameter in the map.') throw new Error("Missing JITSI_URL environment variable or jitsiUrl parameter in the map.");
} }
await this.loadJitsiScript(domain); await this.loadJitsiScript(domain);
const options: any = { // eslint-disable-line @typescript-eslint/no-explicit-any const options: JitsiOptions = {
roomName: roomName, roomName: roomName,
jwt: jwt, jwt: jwt,
width: "100%", width: "100%",
height: "100%", height: "100%",
parentNode: cowebsiteDiv, parentNode: cowebsiteDiv,
configOverwrite: mergeConfig(config), configOverwrite: mergeConfig(config),
interfaceConfigOverwrite: {...defaultInterfaceConfig, ...interfaceConfig} interfaceConfigOverwrite: { ...defaultInterfaceConfig, ...interfaceConfig },
}; };
if (!options.jwt) { if (!options.jwt) {
delete options.jwt; delete options.jwt;
@ -115,25 +173,25 @@ class JitsiFactory {
options.onload = () => resolve(); //we want for the iframe to be loaded before triggering animations. options.onload = () => resolve(); //we want for the iframe to be loaded before triggering animations.
setTimeout(() => resolve(), 2000); //failsafe in case the iframe is deleted before loading or too long to load setTimeout(() => resolve(), 2000); //failsafe in case the iframe is deleted before loading or too long to load
this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options); this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options);
this.jitsiApi.executeCommand('displayName', playerName); this.jitsiApi.executeCommand("displayName", playerName);
this.jitsiApi.addListener('audioMuteStatusChanged', this.audioCallback); this.jitsiApi.addListener("audioMuteStatusChanged", this.audioCallback);
this.jitsiApi.addListener('videoMuteStatusChanged', this.videoCallback); this.jitsiApi.addListener("videoMuteStatusChanged", this.videoCallback);
}); });
}), jitsiWidth); }, jitsiWidth);
} }
public async stop(): Promise<void> { public async stop(): Promise<void> {
if(!this.jitsiApi){ if (!this.jitsiApi) {
return; return;
} }
await coWebsiteManager.closeCoWebsite(); await coWebsiteManager.closeCoWebsite();
this.jitsiApi.removeListener('audioMuteStatusChanged', this.audioCallback); this.jitsiApi.removeListener("audioMuteStatusChanged", this.audioCallback);
this.jitsiApi.removeListener('videoMuteStatusChanged', this.videoCallback); this.jitsiApi.removeListener("videoMuteStatusChanged", this.videoCallback);
this.jitsiApi?.dispose(); this.jitsiApi?.dispose();
} }
private onAudioChange({muted}: {muted: boolean}): void { private onAudioChange({ muted }: { muted: boolean }): void {
if (muted) { if (muted) {
requestedMicrophoneState.disableMicrophone(); requestedMicrophoneState.disableMicrophone();
} else { } else {
@ -141,7 +199,7 @@ class JitsiFactory {
} }
} }
private onVideoChange({muted}: {muted: boolean}): void { private onVideoChange({ muted }: { muted: boolean }): void {
if (muted) { if (muted) {
requestedCameraState.disableWebcam(); requestedCameraState.disableWebcam();
} else { } else {
@ -159,20 +217,17 @@ class JitsiFactory {
this.jitsiScriptLoaded = true; this.jitsiScriptLoaded = true;
// Load Jitsi if the environment variable is set. // Load Jitsi if the environment variable is set.
const jitsiScript = document.createElement('script'); const jitsiScript = document.createElement("script");
jitsiScript.src = 'https://' + domain + '/external_api.js'; jitsiScript.src = "https://" + domain + "/external_api.js";
jitsiScript.onload = () => { jitsiScript.onload = () => {
resolve(); resolve();
} };
jitsiScript.onerror = () => { jitsiScript.onerror = () => {
reject(); reject();
} };
document.head.appendChild(jitsiScript); document.head.appendChild(jitsiScript);
});
})
} }
} }