From 8da5bf9f8ec37037e83945d47305df2efe27d767 Mon Sep 17 00:00:00 2001 From: jonny Date: Sun, 9 May 2021 21:30:29 +0200 Subject: [PATCH 1/3] typed iframe api events # Conflicts: # front/src/Api/IframeListener.ts # front/src/iframe_api.ts --- front/src/Api/Events/IframeEvent.ts | 60 ++++++++++++++++++++-- front/src/Api/IframeListener.ts | 13 ++++- front/src/iframe_api.ts | 78 ++++++++++++++--------------- 3 files changed, 106 insertions(+), 45 deletions(-) diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 883f50fc..18d88ed7 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -1,7 +1,59 @@ -export interface IframeEvent { - type: string; - data: unknown; + + +//import { GameStateEvent } from './ApiGameStateEvent'; +//import { UpdateTileEvent } from './ApiUpdateTileEvent'; +import { ButtonClickedEvent } from './ButtonClickedEvent'; +import { ChatEvent } from './ChatEvent'; +import { ClosePopupEvent } from './ClosePopupEvent'; +import { EnterLeaveEvent } from './EnterLeaveEvent'; +import { GoToPageEvent } from './GoToPageEvent'; +import { LoadPageEvent } from './LoadPageEvent'; +import { MenuItemClickedEvent } from './MenuItemClickedEvent'; +import { MenuItemRegisterEvent } from './MenuItemRegisterEvent'; +import { OpenCoWebSiteEvent } from './OpenCoWebSiteEvent'; +import { OpenPopupEvent } from './OpenPopupEvent'; +import { OpenTabEvent } from './OpenTabEvent'; +import { UserInputChatEvent } from './UserInputChatEvent'; + + +export type IframeEventMap = { + //getState: GameStateEvent, + // updateTile: UpdateTileEvent + chat: ChatEvent, + openPopup: OpenPopupEvent + closePopup: ClosePopupEvent + openTab: OpenTabEvent + goToPage: GoToPageEvent + openCoWebSite: OpenCoWebSiteEvent + closeCoWebSite: null + disablePlayerControl: null + restorePlayerControl: null + displayBubble: null + removeBubble: null + registerMenuCommand: MenuItemRegisterEvent + loadPage: LoadPageEvent +} +export interface IframeEvent { + type: T; + data: IframeEventMap[T]; +} + + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const isIframeEventWrapper = (event: any): event is IframeEvent => typeof event.type === 'string'; + +export interface IframeResponseEventMap { + menuItemClicked: MenuItemClickedEvent + userInputChat: UserInputChatEvent + enterEvent: EnterLeaveEvent + leaveEvent: EnterLeaveEvent + buttonClickedEvent: ButtonClickedEvent + // gameState: GameStateEvent +} +export interface IframeResponseEvent { + type: T; + data: IframeResponseEventMap[T]; } // eslint-disable-next-line @typescript-eslint/no-explicit-any -export const isIframeEventWrapper = (event: any): event is IframeEvent => typeof event.type === 'string'; +export const isIframeResponseEventWrapper = (event: { type?: string }): event is IframeResponseEvent => typeof event.type === 'string'; \ No newline at end of file diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 7e51a281..651869d7 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -12,6 +12,15 @@ import {ClosePopupEvent, isClosePopupEvent} from "./Events/ClosePopupEvent"; import {scriptUtils} from "./ScriptUtils"; import {GoToPageEvent, isGoToPageEvent} from "./Events/GoToPageEvent"; import {isOpenCoWebsite, OpenCoWebSiteEvent} from "./Events/OpenCoWebSiteEvent"; +import { IframeEventMap, IframeEvent, IframeResponseEvent, IframeResponseEventMap, isIframeEventWrapper } from "./Events/IframeEvent"; +import { isLoadPageEvent } from './Events/LoadPageEvent'; +import { MenuItemClickedEvent } from './Events/MenuItemClickedEvent'; +import { isMenuItemRegisterEvent } from './Events/MenuItemRegisterEvent'; +import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent"; +import { isOpenPopupEvent, OpenPopupEvent } from "./Events/OpenPopupEvent"; +import { isOpenTabEvent, OpenTabEvent } from "./Events/OpenTabEvent"; +import { UserInputChatEvent } from "./Events/UserInputChatEvent"; +import { scriptUtils } from "./ScriptUtils"; /** @@ -56,7 +65,7 @@ class IframeListener { private readonly scripts = new Map(); init() { - window.addEventListener("message", (message) => { + window.addEventListener("message", (message: MessageEvent>) => { // Do we trust the sender of this message? // 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). @@ -231,7 +240,7 @@ class IframeListener { /** * Sends the message... to all allowed iframes. */ - private postMessage(message: IframeEvent) { + private postMessage(message: IframeResponseEvent) { for (const iframe of this.iframes) { iframe.contentWindow?.postMessage(message, '*'); } diff --git a/front/src/iframe_api.ts b/front/src/iframe_api.ts index 18d8d172..6d0ef354 100644 --- a/front/src/iframe_api.ts +++ b/front/src/iframe_api.ts @@ -1,14 +1,14 @@ -import {ChatEvent, isChatEvent} from "./Api/Events/ChatEvent"; -import {isIframeEventWrapper} from "./Api/Events/IframeEvent"; -import {isUserInputChatEvent, UserInputChatEvent} from "./Api/Events/UserInputChatEvent"; -import {Subject} from "rxjs"; -import {EnterLeaveEvent, isEnterLeaveEvent} from "./Api/Events/EnterLeaveEvent"; -import {OpenPopupEvent} from "./Api/Events/OpenPopupEvent"; -import {isButtonClickedEvent} from "./Api/Events/ButtonClickedEvent"; -import {ClosePopupEvent} from "./Api/Events/ClosePopupEvent"; -import {OpenTabEvent} from "./Api/Events/OpenTabEvent"; -import {GoToPageEvent} from "./Api/Events/GoToPageEvent"; -import {OpenCoWebSiteEvent} from "./Api/Events/OpenCoWebSiteEvent"; +import { ChatEvent } from "./Api/Events/ChatEvent"; +import { isIframeResponseEventWrapper } from "./Api/Events/IframeEvent"; +import { isUserInputChatEvent, UserInputChatEvent } from "./Api/Events/UserInputChatEvent"; +import { Subject } from "rxjs"; +import { EnterLeaveEvent, isEnterLeaveEvent } from "./Api/Events/EnterLeaveEvent"; +import { OpenPopupEvent } from "./Api/Events/OpenPopupEvent"; +import { isButtonClickedEvent } from "./Api/Events/ButtonClickedEvent"; +import { ClosePopupEvent } from "./Api/Events/ClosePopupEvent"; +import { OpenTabEvent } from "./Api/Events/OpenTabEvent"; +import { GoToPageEvent } from "./Api/Events/GoToPageEvent"; +import { OpenCoWebSiteEvent } from "./Api/Events/OpenCoWebSiteEvent"; interface WorkAdventureApi { sendChatMessage(message: string, author: string): void; @@ -20,10 +20,10 @@ interface WorkAdventureApi { goToPage(url : string): void; openCoWebSite(url : string): void; closeCoWebSite(): void; - disablePlayerControl() : void; - restorePlayerControl() : void; - displayBubble() : void; - removeBubble() : void; + disablePlayerControl(): void; + restorePlayerControl(): void; + displayBubble(): void; + removeBubble(): void; } declare global { @@ -50,7 +50,7 @@ interface ButtonDescriptor { /** * The type of the button. Can be one of "normal", "primary", "success", "warning", "error", "disabled" */ - className?: "normal"|"primary"|"success"|"warning"|"error"|"disabled", + className?: "normal" | "primary" | "success" | "warning" | "error" | "disabled", /** * Callback called if the button is pressed */ @@ -88,38 +88,38 @@ window.WA = { } as ChatEvent }, '*'); }, - disablePlayerControl() : void { - window.parent.postMessage({'type' : 'disablePlayerControl'},'*'); + disablePlayerControl(): void { + window.parent.postMessage({ 'type': 'disablePlayerControl' }, '*'); }, - restorePlayerControl() : void { - window.parent.postMessage({'type' : 'restorePlayerControl'},'*'); + restorePlayerControl(): void { + window.parent.postMessage({ 'type': 'restorePlayerControl' }, '*'); }, - displayBubble() : void { - window.parent.postMessage({'type' : 'displayBubble'},'*'); + displayBubble(): void { + window.parent.postMessage({ 'type': 'displayBubble' }, '*'); }, - removeBubble() : void { - window.parent.postMessage({'type' : 'removeBubble'},'*'); + removeBubble(): void { + window.parent.postMessage({ 'type': 'removeBubble' }, '*'); }, - openTab(url : string) : void{ + openTab(url: string): void { window.parent.postMessage({ - "type" : 'openTab', - "data" : { + "type": 'openTab', + "data": { url } as OpenTabEvent - },'*'); + }, '*'); }, - goToPage(url : string) : void{ + goToPage(url: string): void { window.parent.postMessage({ - "type" : 'goToPage', - "data" : { + "type": 'goToPage', + "data": { url } as GoToPageEvent - },'*'); + }, '*'); }, openCoWebSite(url : string) : void{ @@ -128,13 +128,13 @@ window.WA = { "data" : { url } as OpenCoWebSiteEvent - },'*'); + }, '*'); }, - closeCoWebSite() : void{ + closeCoWebSite(): void { window.parent.postMessage({ - "type" : 'closeCoWebSite' - },'*'); + "type": 'closeCoWebSite' + }, '*'); }, openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup { @@ -205,9 +205,9 @@ window.addEventListener('message', message => { const payload = message.data; - console.log(payload); + console.debug(payload); - if (isIframeEventWrapper(payload)) { + if (isIframeResponseEventWrapper(payload)) { const payloadData = payload.data; if (payload.type === 'userInputChat' && isUserInputChatEvent(payloadData)) { userInputChatStream.next(payloadData); @@ -219,7 +219,7 @@ window.addEventListener('message', message => { const callback = popupCallbacks.get(payloadData.popupId)?.get(payloadData.buttonId); const popup = popups.get(payloadData.popupId); if (popup === undefined) { - throw new Error('Could not find popup with ID "'+payloadData.popupId+'"'); + throw new Error('Could not find popup with ID "' + payloadData.popupId + '"'); } if (callback) { callback(popup); From cf06f29ef81a83635ba01f307046fd04f283925a Mon Sep 17 00:00:00 2001 From: jonny Date: Sun, 9 May 2021 21:38:11 +0200 Subject: [PATCH 2/3] fixed cherry pick conflicts --- front/src/Api/Events/IframeEvent.ts | 8 ----- front/src/Api/IframeListener.ts | 53 ++++++++++++----------------- 2 files changed, 22 insertions(+), 39 deletions(-) diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 18d88ed7..0657b15a 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -1,15 +1,10 @@ -//import { GameStateEvent } from './ApiGameStateEvent'; -//import { UpdateTileEvent } from './ApiUpdateTileEvent'; import { ButtonClickedEvent } from './ButtonClickedEvent'; import { ChatEvent } from './ChatEvent'; import { ClosePopupEvent } from './ClosePopupEvent'; import { EnterLeaveEvent } from './EnterLeaveEvent'; import { GoToPageEvent } from './GoToPageEvent'; -import { LoadPageEvent } from './LoadPageEvent'; -import { MenuItemClickedEvent } from './MenuItemClickedEvent'; -import { MenuItemRegisterEvent } from './MenuItemRegisterEvent'; import { OpenCoWebSiteEvent } from './OpenCoWebSiteEvent'; import { OpenPopupEvent } from './OpenPopupEvent'; import { OpenTabEvent } from './OpenTabEvent'; @@ -30,8 +25,6 @@ export type IframeEventMap = { restorePlayerControl: null displayBubble: null removeBubble: null - registerMenuCommand: MenuItemRegisterEvent - loadPage: LoadPageEvent } export interface IframeEvent { type: T; @@ -43,7 +36,6 @@ export interface IframeEvent { export const isIframeEventWrapper = (event: any): event is IframeEvent => typeof event.type === 'string'; export interface IframeResponseEventMap { - menuItemClicked: MenuItemClickedEvent userInputChat: UserInputChatEvent enterEvent: EnterLeaveEvent leaveEvent: EnterLeaveEvent diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 651869d7..03f81210 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -1,26 +1,17 @@ -import {Subject} from "rxjs"; -import {ChatEvent, isChatEvent} from "./Events/ChatEvent"; -import {IframeEvent, isIframeEventWrapper} from "./Events/IframeEvent"; -import {UserInputChatEvent} from "./Events/UserInputChatEvent"; +import { Subject } from "rxjs"; +import { ChatEvent, isChatEvent } from "./Events/ChatEvent"; import * as crypto from "crypto"; -import {HtmlUtils} from "../WebRtc/HtmlUtils"; -import {EnterLeaveEvent} from "./Events/EnterLeaveEvent"; -import {isOpenPopupEvent, OpenPopupEvent} from "./Events/OpenPopupEvent"; -import {isOpenTabEvent, OpenTabEvent} from "./Events/OpenTabEvent"; -import {ButtonClickedEvent} from "./Events/ButtonClickedEvent"; -import {ClosePopupEvent, isClosePopupEvent} from "./Events/ClosePopupEvent"; -import {scriptUtils} from "./ScriptUtils"; -import {GoToPageEvent, isGoToPageEvent} from "./Events/GoToPageEvent"; -import {isOpenCoWebsite, OpenCoWebSiteEvent} from "./Events/OpenCoWebSiteEvent"; -import { IframeEventMap, IframeEvent, IframeResponseEvent, IframeResponseEventMap, isIframeEventWrapper } from "./Events/IframeEvent"; -import { isLoadPageEvent } from './Events/LoadPageEvent'; -import { MenuItemClickedEvent } from './Events/MenuItemClickedEvent'; -import { isMenuItemRegisterEvent } from './Events/MenuItemRegisterEvent'; -import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent"; +import { HtmlUtils } from "../WebRtc/HtmlUtils"; +import { EnterLeaveEvent } from "./Events/EnterLeaveEvent"; import { isOpenPopupEvent, OpenPopupEvent } from "./Events/OpenPopupEvent"; import { isOpenTabEvent, OpenTabEvent } from "./Events/OpenTabEvent"; -import { UserInputChatEvent } from "./Events/UserInputChatEvent"; +import { ButtonClickedEvent } from "./Events/ButtonClickedEvent"; +import { ClosePopupEvent, isClosePopupEvent } from "./Events/ClosePopupEvent"; import { scriptUtils } from "./ScriptUtils"; +import { GoToPageEvent, isGoToPageEvent } from "./Events/GoToPageEvent"; +import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent"; +import { IframeEventMap, IframeEvent, IframeResponseEvent, IframeResponseEventMap, isIframeEventWrapper } from "./Events/IframeEvent"; +import { UserInputChatEvent } from "./Events/UserInputChatEvent"; /** @@ -89,10 +80,10 @@ class IframeListener { } else if (payload.type === 'closePopup' && isClosePopupEvent(payload.data)) { this._closePopupStream.next(payload.data); } - else if(payload.type === 'openTab' && isOpenTabEvent(payload.data)) { + else if (payload.type === 'openTab' && isOpenTabEvent(payload.data)) { scriptUtils.openTab(payload.data.url); } - else if(payload.type === 'goToPage' && isGoToPageEvent(payload.data)) { + else if (payload.type === 'goToPage' && isGoToPageEvent(payload.data)) { scriptUtils.goToPage(payload.data.url); } else if (payload.type === 'openCoWebSite' && isOpenCoWebsite(payload.data)) { @@ -102,19 +93,19 @@ class IframeListener { scriptUtils.openCoWebsite(payload.data.url, scriptUrl || foundSrc); } - else if(payload.type === 'closeCoWebSite') { + else if (payload.type === 'closeCoWebSite') { scriptUtils.closeCoWebSite(); } - else if (payload.type === 'disablePlayerControl'){ + else if (payload.type === 'disablePlayerControl') { this._disablePlayerControlStream.next(); } - else if (payload.type === 'restorePlayerControl'){ + else if (payload.type === 'restorePlayerControl') { this._enablePlayerControlStream.next(); } - else if (payload.type === 'displayBubble'){ + else if (payload.type === 'displayBubble') { this._displayBubbleStream.next(); } - else if (payload.type === 'removeBubble'){ + else if (payload.type === 'removeBubble') { this._removeBubbleStream.next(); } } @@ -143,7 +134,7 @@ class IframeListener { const iframe = document.createElement('iframe'); iframe.id = this.getIFrameId(scriptUrl); iframe.style.display = 'none'; - iframe.src = '/iframe.html?script='+encodeURIComponent(scriptUrl); + iframe.src = '/iframe.html?script=' + encodeURIComponent(scriptUrl); // We are putting a sandbox on this script because it will run in the same domain as the main website. iframe.sandbox.add('allow-scripts'); @@ -167,8 +158,8 @@ class IframeListener { '\n' + '\n' + '\n' + - '\n' + - '\n' + + '\n' + + '\n' + '\n' + '\n'; @@ -185,14 +176,14 @@ class IframeListener { } private getIFrameId(scriptUrl: string): string { - return 'script'+crypto.createHash('md5').update(scriptUrl).digest("hex"); + return 'script' + crypto.createHash('md5').update(scriptUrl).digest("hex"); } unregisterScript(scriptUrl: string): void { const iFrameId = this.getIFrameId(scriptUrl); const iframe = HtmlUtils.getElementByIdOrFail(iFrameId); if (!iframe) { - throw new Error('Unknown iframe for script "'+scriptUrl+'"'); + throw new Error('Unknown iframe for script "' + scriptUrl + '"'); } this.unregisterIframe(iframe); iframe.remove(); From 91148035ec0f9c221b53f22d74b56222312ae4e1 Mon Sep 17 00:00:00 2001 From: jonny Date: Sun, 9 May 2021 21:46:40 +0200 Subject: [PATCH 3/3] polyfill generic message event --- front/src/Api/Events/IframeEvent.ts | 4 ++++ front/src/Api/IframeListener.ts | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 0657b15a..1ed853c9 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -11,6 +11,10 @@ import { OpenTabEvent } from './OpenTabEvent'; import { UserInputChatEvent } from './UserInputChatEvent'; +export interface TypedMessageEvent extends MessageEvent { + data: T +} + export type IframeEventMap = { //getState: GameStateEvent, // updateTile: UpdateTileEvent diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 03f81210..d3b8ce4f 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -10,7 +10,7 @@ import { ClosePopupEvent, isClosePopupEvent } from "./Events/ClosePopupEvent"; import { scriptUtils } from "./ScriptUtils"; import { GoToPageEvent, isGoToPageEvent } from "./Events/GoToPageEvent"; import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent"; -import { IframeEventMap, IframeEvent, IframeResponseEvent, IframeResponseEventMap, isIframeEventWrapper } from "./Events/IframeEvent"; +import { IframeEventMap, IframeEvent, IframeResponseEvent, IframeResponseEventMap, isIframeEventWrapper, TypedMessageEvent } from "./Events/IframeEvent"; import { UserInputChatEvent } from "./Events/UserInputChatEvent"; @@ -56,7 +56,7 @@ class IframeListener { private readonly scripts = new Map(); init() { - window.addEventListener("message", (message: MessageEvent>) => { + window.addEventListener("message", (message: TypedMessageEvent>) => { // Do we trust the sender of this message? // 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).