From eb93a043415fc9e2d67c06c7bb29c5018cc5b225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 4 Mar 2021 19:00:00 +0100 Subject: [PATCH 1/8] Adding an API for inter-iframe communication Adds a first version of an API to communicate between an iFrame opened by WorkAdventure and WorkAdventure itself. The first API method is a method allowing to add messages in the chat, from the iFrame. Comes with a test file. --- front/dist/.gitignore | 3 +- front/src/Api/IframeListener.ts | 34 ++++++++++ front/src/WebRtc/DiscussionManager.ts | 6 ++ front/src/iframe_api.ts | 21 +++++++ front/src/index.ts | 4 ++ front/webpack.config.js | 14 ++++- maps/tests/iframe.html | 17 +++++ maps/tests/iframe_api.json | 89 +++++++++++++++++++++++++++ 8 files changed, 184 insertions(+), 4 deletions(-) create mode 100644 front/src/Api/IframeListener.ts create mode 100644 front/src/iframe_api.ts create mode 100644 maps/tests/iframe.html create mode 100644 maps/tests/iframe_api.json diff --git a/front/dist/.gitignore b/front/dist/.gitignore index 64233a9e..4fc7097a 100644 --- a/front/dist/.gitignore +++ b/front/dist/.gitignore @@ -1 +1,2 @@ -index.html \ No newline at end of file +index.html +/js/ diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts new file mode 100644 index 00000000..2cc1134f --- /dev/null +++ b/front/src/Api/IframeListener.ts @@ -0,0 +1,34 @@ +import {Subject} from "rxjs"; + +interface ChatEvent { + message: string, + author: string +} + +/** + * Listens to messages from iframes and turn those messages into easy to use observables. + */ +class IframeListener { + private readonly _chatStream: Subject = new Subject(); + public readonly chatStream = this._chatStream.asObservable(); + + init() { + window.addEventListener("message", (event) => { + // Do we trust the sender of this message? + //if (event.origin !== "http://example.com:8080") + // return; + + // event.source is window.opener + // event.data is the data sent by the iframe + + // FIXME: this is WAAAAAAAY too sloppy as "any" let's us put anything in the message. + + if (event.data.type === 'chat') { + this._chatStream.next(event.data.data); + } + + }, false); + } +} + +export const iframeListener = new IframeListener(); diff --git a/front/src/WebRtc/DiscussionManager.ts b/front/src/WebRtc/DiscussionManager.ts index 235e124b..824bdfdc 100644 --- a/front/src/WebRtc/DiscussionManager.ts +++ b/front/src/WebRtc/DiscussionManager.ts @@ -3,6 +3,7 @@ import {mediaManager, ReportCallback, ShowReportCallBack} from "./MediaManager"; import {UserInputManager} from "../Phaser/UserInput/UserInputManager"; import {connectionManager} from "../Connexion/ConnectionManager"; import {GameConnexionTypes} from "../Url/UrlManager"; +import {iframeListener} from "../Api/IframeListener"; export type SendMessageCallback = (message:string) => void; @@ -25,6 +26,11 @@ export class DiscussionManager { constructor() { this.mainContainer = HtmlUtils.getElementByIdOrFail('main-container'); this.createDiscussPart(''); //todo: why do we always use empty string? + + iframeListener.chatStream.subscribe((chatEvent) => { + this.addMessage(chatEvent.author, chatEvent.message, false); + this.showDiscussion(); + }); } private createDiscussPart(name: string) { diff --git a/front/src/iframe_api.ts b/front/src/iframe_api.ts new file mode 100644 index 00000000..d7aaf13e --- /dev/null +++ b/front/src/iframe_api.ts @@ -0,0 +1,21 @@ +interface WorkAdventureApi { + sendChatMessage(message: string, author: string): void; +} + +declare var WA: WorkAdventureApi; + +window.WA = { + /** + * Sends a message in the chat. + * Only the local user will receive this message. + */ + sendChatMessage(message: string, author: string) { + window.parent.postMessage({ + 'type': 'chat', + 'data': { + 'message': message, + 'author': author + } + }, '*'); + } +} diff --git a/front/src/index.ts b/front/src/index.ts index 94b1df43..b00f5915 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -14,6 +14,8 @@ import {coWebsiteManager} from "./WebRtc/CoWebsiteManager"; import {MenuScene} from "./Phaser/Menu/MenuScene"; import {localUserStore} from "./Connexion/LocalUserStore"; import {ErrorScene} from "./Phaser/Reconnecting/ErrorScene"; +import {iframeListener} from "./Api/IframeListener"; +import {discussionManager} from "./WebRtc/DiscussionManager"; // Load Jitsi if the environment variable is set. if (JITSI_URL) { @@ -124,3 +126,5 @@ coWebsiteManager.onStateChange(() => { const {width, height} = coWebsiteManager.getGameSize(); game.scale.resize(width / RESOLUTION, height / RESOLUTION); }); + +iframeListener.init(); diff --git a/front/webpack.config.js b/front/webpack.config.js index 170466ef..da686fe2 100644 --- a/front/webpack.config.js +++ b/front/webpack.config.js @@ -3,7 +3,10 @@ const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { - entry: './src/index.ts', + entry: { + 'main': './src/index.ts', + 'iframe_api': './src/iframe_api.ts' + }, devtool: 'inline-source-map', devServer: { contentBase: './dist', @@ -29,7 +32,11 @@ module.exports = { extensions: [ '.tsx', '.ts', '.js' ], }, output: { - filename: '[name].[contenthash].js', + filename: (pathData) => { + // Add a content hash only for the main bundle. + // We want the iframe_api.js file to keep its name as it will be referenced from outside iframes. + return pathData.chunk.name === 'main' ? 'js/[name].[contenthash].js': '[name].js'; + }, path: path.resolve(__dirname, 'dist'), publicPath: '/' }, @@ -48,7 +55,8 @@ module.exports = { removeScriptTypeAttributes: true, removeStyleLinkTypeAttributes: true, useShortDoctype: true - } + }, + chunks: ['main'] } ), new webpack.ProvidePlugin({ diff --git a/maps/tests/iframe.html b/maps/tests/iframe.html new file mode 100644 index 00000000..fd430e18 --- /dev/null +++ b/maps/tests/iframe.html @@ -0,0 +1,17 @@ + + + + + + + + + + + diff --git a/maps/tests/iframe_api.json b/maps/tests/iframe_api.json new file mode 100644 index 00000000..c80c49b6 --- /dev/null +++ b/maps/tests/iframe_api.json @@ -0,0 +1,89 @@ +{ "compressionlevel":-1, + "editorsettings": + { + "export": + { + "target":"." + } + }, + "height":10, + "infinite":false, + "layers":[ + { + "data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + "height":10, + "id":1, + "name":"floor", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":2, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":5, + "name":"iframe_api", + "opacity":1, + "properties":[ + { + "name":"openWebsite", + "type":"string", + "value":"iframe.html" + }], + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":3, + "name":"floorLayer", + "objects":[], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":6, + "nextobjectid":1, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.3.3", + "tileheight":32, + "tilesets":[ + { + "columns":11, + "firstgid":1, + "image":"tileset1.png", + "imageheight":352, + "imagewidth":352, + "margin":0, + "name":"tileset1", + "spacing":0, + "tilecount":121, + "tileheight":32, + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":1.2, + "width":10 +} \ No newline at end of file From 83fa23a82d2a8aee3a0de8f7ee6189f653396aa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 5 Mar 2021 11:47:04 +0100 Subject: [PATCH 2/8] Fixing eslint --- front/src/iframe_api.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/front/src/iframe_api.ts b/front/src/iframe_api.ts index d7aaf13e..8810a92a 100644 --- a/front/src/iframe_api.ts +++ b/front/src/iframe_api.ts @@ -2,6 +2,7 @@ interface WorkAdventureApi { sendChatMessage(message: string, author: string): void; } +// eslint-disable-next-line no-var declare var WA: WorkAdventureApi; window.WA = { From 5178dff1086ef255a82de8616603f868a45b3a6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 5 Mar 2021 16:50:54 +0100 Subject: [PATCH 3/8] Adding strong checks on messages exchanged using generic-type-guard --- deeployer.libsonnet | 2 -- front/src/Api/Events/ChatEvent.ts | 8 ++++++++ front/src/Api/Events/IframeEvent.ts | 7 +++++++ front/src/Api/IframeListener.ts | 17 +++++++++-------- front/src/iframe_api.ts | 19 +++++++++++++++---- 5 files changed, 39 insertions(+), 14 deletions(-) create mode 100644 front/src/Api/Events/ChatEvent.ts create mode 100644 front/src/Api/Events/IframeEvent.ts diff --git a/deeployer.libsonnet b/deeployer.libsonnet index 5093c86a..4b606218 100644 --- a/deeployer.libsonnet +++ b/deeployer.libsonnet @@ -79,8 +79,6 @@ "JITSI_URL": env.JITSI_URL, "SECRET_JITSI_KEY": env.SECRET_JITSI_KEY, "TURN_SERVER": "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443", - "TURN_USER": "workadventure", - "TURN_PASSWORD": "WorkAdventure123", "JITSI_PRIVATE_MODE": if env.SECRET_JITSI_KEY != '' then "true" else "false", "START_ROOM_URL": "/_/global/maps."+url+"/Floor0/floor0.json" //"GA_TRACKING_ID": "UA-10196481-11" diff --git a/front/src/Api/Events/ChatEvent.ts b/front/src/Api/Events/ChatEvent.ts new file mode 100644 index 00000000..6242b871 --- /dev/null +++ b/front/src/Api/Events/ChatEvent.ts @@ -0,0 +1,8 @@ +import * as tg from "generic-type-guard"; + +export const isChatEvent = + new tg.IsInterface().withProperties({ + message: tg.isString, + author: tg.isString, + }).get(); +export type ChatEvent = tg.GuardedType; diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts new file mode 100644 index 00000000..fb63e7cf --- /dev/null +++ b/front/src/Api/Events/IframeEvent.ts @@ -0,0 +1,7 @@ +interface IframeEvent { + type: string; + data: unknown; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const isIframeEventWrapper = (event: any): event is IframeEvent => typeof event.type === 'string' && typeof event.data === 'object'; diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 2cc1134f..0e67433c 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -1,9 +1,8 @@ import {Subject} from "rxjs"; +import {ChatEvent, isChatEvent} from "./Events/ChatEvent"; +import {isIframeEventWrapper} from "./Events/IframeEvent"; + -interface ChatEvent { - message: string, - author: string -} /** * Listens to messages from iframes and turn those messages into easy to use observables. @@ -21,12 +20,14 @@ class IframeListener { // event.source is window.opener // event.data is the data sent by the iframe - // FIXME: this is WAAAAAAAY too sloppy as "any" let's us put anything in the message. - - if (event.data.type === 'chat') { - this._chatStream.next(event.data.data); + const payload = event.data; + if (isIframeEventWrapper(payload)) { + if (payload.type === 'chat' && isChatEvent(payload.data)) { + this._chatStream.next(payload.data); + } } + }, false); } } diff --git a/front/src/iframe_api.ts b/front/src/iframe_api.ts index 8810a92a..9f981d3a 100644 --- a/front/src/iframe_api.ts +++ b/front/src/iframe_api.ts @@ -1,13 +1,18 @@ +import {ChatEvent} from "./Api/Events/ChatEvent"; + interface WorkAdventureApi { sendChatMessage(message: string, author: string): void; + onChatMessage(callback: (message: string) => void): void; } -// eslint-disable-next-line no-var -declare var WA: WorkAdventureApi; +declare global { + // eslint-disable-next-line no-var + var WA: WorkAdventureApi +} window.WA = { /** - * Sends a message in the chat. + * Send a message in the chat. * Only the local user will receive this message. */ sendChatMessage(message: string, author: string) { @@ -16,7 +21,13 @@ window.WA = { 'data': { 'message': message, 'author': author - } + } as ChatEvent }, '*'); + }, + /** + * Listen to messages sent by the local user, in the chat. + */ + onChatMessage(callback: (message: string) => void): void { + } } From e927e0fa169efd4a6de8524705e541226a9b2ee7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Sat, 6 Mar 2021 15:26:07 +0100 Subject: [PATCH 4/8] Adding ability to listen to user types chat messages using WA.onChatMessage --- front/src/Api/Events/ChatEvent.ts | 3 ++ front/src/Api/Events/IframeEvent.ts | 2 +- front/src/Api/Events/UserInputChatEvent.ts | 10 +++++++ front/src/Api/IframeListener.ts | 34 ++++++++++++++++++---- front/src/WebRtc/DiscussionManager.ts | 3 ++ front/src/iframe_api.ts | 32 ++++++++++++++++++-- maps/tests/iframe.html | 8 +++++ 7 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 front/src/Api/Events/UserInputChatEvent.ts diff --git a/front/src/Api/Events/ChatEvent.ts b/front/src/Api/Events/ChatEvent.ts index 6242b871..5729a120 100644 --- a/front/src/Api/Events/ChatEvent.ts +++ b/front/src/Api/Events/ChatEvent.ts @@ -5,4 +5,7 @@ export const isChatEvent = message: tg.isString, author: tg.isString, }).get(); +/** + * A message sent from the iFrame to the game to add a message in the chat. + */ export type ChatEvent = tg.GuardedType; diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index fb63e7cf..65d2b443 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -1,4 +1,4 @@ -interface IframeEvent { +export interface IframeEvent { type: string; data: unknown; } diff --git a/front/src/Api/Events/UserInputChatEvent.ts b/front/src/Api/Events/UserInputChatEvent.ts new file mode 100644 index 00000000..de21ff6e --- /dev/null +++ b/front/src/Api/Events/UserInputChatEvent.ts @@ -0,0 +1,10 @@ +import * as tg from "generic-type-guard"; + +export const isUserInputChatEvent = + new tg.IsInterface().withProperties({ + message: tg.isString, + }).get(); +/** + * A message sent from the game to the iFrame when a user types a message in the chat. + */ +export type UserInputChatEvent = tg.GuardedType; diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 0e67433c..a94d294c 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -1,6 +1,7 @@ import {Subject} from "rxjs"; import {ChatEvent, isChatEvent} from "./Events/ChatEvent"; -import {isIframeEventWrapper} from "./Events/IframeEvent"; +import {IframeEvent, isIframeEventWrapper} from "./Events/IframeEvent"; +import {UserInputChatEvent} from "./Events/UserInputChatEvent"; @@ -12,15 +13,15 @@ class IframeListener { public readonly chatStream = this._chatStream.asObservable(); init() { - window.addEventListener("message", (event) => { + window.addEventListener("message", (message) => { // Do we trust the sender of this message? - //if (event.origin !== "http://example.com:8080") + //if (message.origin !== "http://example.com:8080") // return; - // event.source is window.opener - // event.data is the data sent by the iframe + // message.source is window.opener + // message.data is the data sent by the iframe - const payload = event.data; + const payload = message.data; if (isIframeEventWrapper(payload)) { if (payload.type === 'chat' && isChatEvent(payload.data)) { this._chatStream.next(payload.data); @@ -29,6 +30,27 @@ class IframeListener { }, false); + + + } + + sendUserInputChat(message: string) { + this.postMessage({ + 'type': 'userInputChat', + 'data': { + 'message': message, + } as UserInputChatEvent + }); + } + + /** + * Sends the message... to absolutely all the iFrames that can be found in the current document. + */ + private postMessage(message: IframeEvent) { + // TODO: not the most effecient implementation if there are many events sent! + for (const iframe of document.querySelectorAll('iframe')) { + iframe.contentWindow?.postMessage(message, '*'); + } } } diff --git a/front/src/WebRtc/DiscussionManager.ts b/front/src/WebRtc/DiscussionManager.ts index 824bdfdc..e27cecf7 100644 --- a/front/src/WebRtc/DiscussionManager.ts +++ b/front/src/WebRtc/DiscussionManager.ts @@ -31,6 +31,9 @@ export class DiscussionManager { this.addMessage(chatEvent.author, chatEvent.message, false); this.showDiscussion(); }); + this.onSendMessageCallback('iframe_listener', (message) => { + iframeListener.sendUserInputChat(message); + }) } private createDiscussPart(name: string) { diff --git a/front/src/iframe_api.ts b/front/src/iframe_api.ts index 9f981d3a..1a442661 100644 --- a/front/src/iframe_api.ts +++ b/front/src/iframe_api.ts @@ -1,4 +1,7 @@ -import {ChatEvent} from "./Api/Events/ChatEvent"; +import {ChatEvent, isChatEvent} from "./Api/Events/ChatEvent"; +import {isIframeEventWrapper} from "./Api/Events/IframeEvent"; +import {isUserInputChatEvent, UserInputChatEvent} from "./Api/Events/UserInputChatEvent"; +import {Subject} from "rxjs"; interface WorkAdventureApi { sendChatMessage(message: string, author: string): void; @@ -10,6 +13,11 @@ declare global { var WA: WorkAdventureApi } +type ChatMessageCallback = (message: string) => void; + +const userInputChatStream: Subject = new Subject(); + + window.WA = { /** * Send a message in the chat. @@ -27,7 +35,25 @@ window.WA = { /** * Listen to messages sent by the local user, in the chat. */ - onChatMessage(callback: (message: string) => void): void { - + onChatMessage(callback: ChatMessageCallback): void { + userInputChatStream.subscribe((userInputChatEvent) => { + callback(userInputChatEvent.message); + }); } } + +window.addEventListener('message', message => { + if (message.source !== window.parent) { + console.log('MESSAGE SKIPPED!!!') + return; // Skip message in this event listener + } + + const payload = message.data; + if (isIframeEventWrapper(payload)) { + if (payload.type === 'userInputChat' && isUserInputChatEvent(payload.data)) { + userInputChatStream.next(payload.data); + } + } + + // ... +}); diff --git a/maps/tests/iframe.html b/maps/tests/iframe.html index fd430e18..23bfb479 100644 --- a/maps/tests/iframe.html +++ b/maps/tests/iframe.html @@ -13,5 +13,13 @@ WA.sendChatMessage('Hello world!', 'Mr Robot'); } +
+ From 7d67f5501207f02629ba1dfbcbf2de2905584148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Sat, 6 Mar 2021 16:00:07 +0100 Subject: [PATCH 5/8] Improving security: only iframes opened with "openWebsiteAllowApi" property are now able to send/receive messages. --- front/src/Api/IframeListener.ts | 35 +++++++++++++++++++++------- front/src/Phaser/Game/GameScene.ts | 2 +- front/src/WebRtc/CoWebsiteManager.ts | 26 ++++++++++++++------- maps/tests/iframe_api.json | 5 ++++ 4 files changed, 50 insertions(+), 18 deletions(-) diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index a94d294c..e91b92f3 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -7,19 +7,29 @@ import {UserInputChatEvent} from "./Events/UserInputChatEvent"; /** * Listens to messages from iframes and turn those messages into easy to use observables. + * Also allows to send messages to those iframes. */ class IframeListener { private readonly _chatStream: Subject = new Subject(); public readonly chatStream = this._chatStream.asObservable(); + private readonly iframes = new Set(); + init() { window.addEventListener("message", (message) => { // Do we trust the sender of this message? - //if (message.origin !== "http://example.com:8080") - // return; - - // message.source is window.opener - // message.data is the data sent by the iframe + // Let's only accept messages from the iframe that are allowed. + // Note: maybe we could restrict on the domain too for additional security (in case the iframe goes to another domain). + let found = false; + for (const iframe of this.iframes) { + if (iframe.contentWindow === message.source) { + found = true; + break; + } + } + if (!found) { + return; + } const payload = message.data; if (isIframeEventWrapper(payload)) { @@ -31,7 +41,17 @@ class IframeListener { }, false); + } + /** + * Allows the passed iFrame to send/receive messages via the API. + */ + registerIframe(iframe: HTMLIFrameElement): void { + this.iframes.add(iframe); + } + + unregisterIframe(iframe: HTMLIFrameElement): void { + this.iframes.delete(iframe); } sendUserInputChat(message: string) { @@ -44,11 +64,10 @@ class IframeListener { } /** - * Sends the message... to absolutely all the iFrames that can be found in the current document. + * Sends the message... to all allowed iframes. */ private postMessage(message: IframeEvent) { - // TODO: not the most effecient implementation if there are many events sent! - for (const iframe of document.querySelectorAll('iframe')) { + for (const iframe of this.iframes) { iframe.contentWindow?.postMessage(message, '*'); } } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 2561a636..5ca3c749 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -654,7 +654,7 @@ export class GameScene extends ResizableScene implements CenterListener { coWebsiteManager.closeCoWebsite(); }else{ const openWebsiteFunction = () => { - coWebsiteManager.loadCoWebsite(newValue as string, this.MapUrlFile, allProps.get('openWebsitePolicy') as string | undefined); + coWebsiteManager.loadCoWebsite(newValue as string, this.MapUrlFile, allProps.get('openWebsiteAllowApi') as boolean | undefined, allProps.get('openWebsitePolicy') as string | undefined); layoutManager.removeActionButton('openWebsite', this.userInputManager); }; diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts index 4e74c4a7..472e7a13 100644 --- a/front/src/WebRtc/CoWebsiteManager.ts +++ b/front/src/WebRtc/CoWebsiteManager.ts @@ -1,4 +1,5 @@ import {HtmlUtils} from "./HtmlUtils"; +import {iframeListener} from "../Api/IframeListener"; export type CoWebsiteStateChangedCallback = () => void; @@ -12,8 +13,8 @@ const cowebsiteDivId = "cowebsite"; // the id of the parent div of the iframe. const animationTime = 500; //time used by the css transitions, in ms. class CoWebsiteManager { - - private opened: iframeStates = iframeStates.closed; + + private opened: iframeStates = iframeStates.closed; private observers = new Array(); /** @@ -21,12 +22,12 @@ class CoWebsiteManager { * So we use this promise to queue up every cowebsite state transition */ private currentOperationPromise: Promise = Promise.resolve(); - private cowebsiteDiv: HTMLDivElement; - + private cowebsiteDiv: HTMLDivElement; + constructor() { this.cowebsiteDiv = HtmlUtils.getElementByIdOrFail(cowebsiteDivId); } - + private close(): void { this.cowebsiteDiv.classList.remove('loaded'); //edit the css class to trigger the transition this.cowebsiteDiv.classList.add('hidden'); @@ -42,7 +43,7 @@ class CoWebsiteManager { this.opened = iframeStates.opened; } - public loadCoWebsite(url: string, base: string, allowPolicy?: string): void { + public loadCoWebsite(url: string, base: string, allowApi?: boolean, allowPolicy?: string): void { this.load(); this.cowebsiteDiv.innerHTML = `