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