Merge pull request #1006 from jonnytest1/typed-api-events
Typed api events
This commit is contained in:
commit
44eb25e9f6
@ -1,7 +1,55 @@
|
|||||||
export interface IframeEvent {
|
|
||||||
type: string;
|
|
||||||
data: unknown;
|
import { ButtonClickedEvent } from './ButtonClickedEvent';
|
||||||
|
import { ChatEvent } from './ChatEvent';
|
||||||
|
import { ClosePopupEvent } from './ClosePopupEvent';
|
||||||
|
import { EnterLeaveEvent } from './EnterLeaveEvent';
|
||||||
|
import { GoToPageEvent } from './GoToPageEvent';
|
||||||
|
import { OpenCoWebSiteEvent } from './OpenCoWebSiteEvent';
|
||||||
|
import { OpenPopupEvent } from './OpenPopupEvent';
|
||||||
|
import { OpenTabEvent } from './OpenTabEvent';
|
||||||
|
import { UserInputChatEvent } from './UserInputChatEvent';
|
||||||
|
|
||||||
|
|
||||||
|
export interface TypedMessageEvent<T> extends MessageEvent {
|
||||||
|
data: T
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
export interface IframeEvent<T extends keyof IframeEventMap> {
|
||||||
|
type: T;
|
||||||
|
data: IframeEventMap[T];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export const isIframeEventWrapper = (event: any): event is IframeEvent<keyof IframeEventMap> => typeof event.type === 'string';
|
||||||
|
|
||||||
|
export interface IframeResponseEventMap {
|
||||||
|
userInputChat: UserInputChatEvent
|
||||||
|
enterEvent: EnterLeaveEvent
|
||||||
|
leaveEvent: EnterLeaveEvent
|
||||||
|
buttonClickedEvent: ButtonClickedEvent
|
||||||
|
// gameState: GameStateEvent
|
||||||
|
}
|
||||||
|
export interface IframeResponseEvent<T extends keyof IframeResponseEventMap> {
|
||||||
|
type: T;
|
||||||
|
data: IframeResponseEventMap[T];
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// 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<keyof IframeResponseEventMap> => typeof event.type === 'string';
|
@ -1,17 +1,17 @@
|
|||||||
import {Subject} from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
import {ChatEvent, isChatEvent} from "./Events/ChatEvent";
|
import { ChatEvent, isChatEvent } from "./Events/ChatEvent";
|
||||||
import {IframeEvent, isIframeEventWrapper} from "./Events/IframeEvent";
|
|
||||||
import {UserInputChatEvent} from "./Events/UserInputChatEvent";
|
|
||||||
import * as crypto from "crypto";
|
import * as crypto from "crypto";
|
||||||
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
import { HtmlUtils } from "../WebRtc/HtmlUtils";
|
||||||
import {EnterLeaveEvent} from "./Events/EnterLeaveEvent";
|
import { EnterLeaveEvent } from "./Events/EnterLeaveEvent";
|
||||||
import {isOpenPopupEvent, OpenPopupEvent} from "./Events/OpenPopupEvent";
|
import { isOpenPopupEvent, OpenPopupEvent } from "./Events/OpenPopupEvent";
|
||||||
import {isOpenTabEvent, OpenTabEvent} from "./Events/OpenTabEvent";
|
import { isOpenTabEvent, OpenTabEvent } from "./Events/OpenTabEvent";
|
||||||
import {ButtonClickedEvent} from "./Events/ButtonClickedEvent";
|
import { ButtonClickedEvent } from "./Events/ButtonClickedEvent";
|
||||||
import {ClosePopupEvent, isClosePopupEvent} from "./Events/ClosePopupEvent";
|
import { ClosePopupEvent, isClosePopupEvent } from "./Events/ClosePopupEvent";
|
||||||
import {scriptUtils} from "./ScriptUtils";
|
import { scriptUtils } from "./ScriptUtils";
|
||||||
import {GoToPageEvent, isGoToPageEvent} from "./Events/GoToPageEvent";
|
import { GoToPageEvent, isGoToPageEvent } from "./Events/GoToPageEvent";
|
||||||
import {isOpenCoWebsite, OpenCoWebSiteEvent} from "./Events/OpenCoWebSiteEvent";
|
import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent";
|
||||||
|
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<string, HTMLIFrameElement>();
|
private readonly scripts = new Map<string, HTMLIFrameElement>();
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
window.addEventListener("message", (message) => {
|
window.addEventListener("message", (message: TypedMessageEvent<IframeEvent<keyof IframeEventMap>>) => {
|
||||||
// Do we trust the sender of this message?
|
// Do we trust the sender of this message?
|
||||||
// Let's only accept messages from the iframe that are allowed.
|
// 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).
|
// Note: maybe we could restrict on the domain too for additional security (in case the iframe goes to another domain).
|
||||||
@ -80,10 +80,10 @@ class IframeListener {
|
|||||||
} else if (payload.type === 'closePopup' && isClosePopupEvent(payload.data)) {
|
} else if (payload.type === 'closePopup' && isClosePopupEvent(payload.data)) {
|
||||||
this._closePopupStream.next(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);
|
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);
|
scriptUtils.goToPage(payload.data.url);
|
||||||
}
|
}
|
||||||
else if (payload.type === 'openCoWebSite' && isOpenCoWebsite(payload.data)) {
|
else if (payload.type === 'openCoWebSite' && isOpenCoWebsite(payload.data)) {
|
||||||
@ -93,19 +93,19 @@ class IframeListener {
|
|||||||
|
|
||||||
scriptUtils.openCoWebsite(payload.data.url, scriptUrl || foundSrc);
|
scriptUtils.openCoWebsite(payload.data.url, scriptUrl || foundSrc);
|
||||||
}
|
}
|
||||||
else if(payload.type === 'closeCoWebSite') {
|
else if (payload.type === 'closeCoWebSite') {
|
||||||
scriptUtils.closeCoWebSite();
|
scriptUtils.closeCoWebSite();
|
||||||
}
|
}
|
||||||
else if (payload.type === 'disablePlayerControl'){
|
else if (payload.type === 'disablePlayerControl') {
|
||||||
this._disablePlayerControlStream.next();
|
this._disablePlayerControlStream.next();
|
||||||
}
|
}
|
||||||
else if (payload.type === 'restorePlayerControl'){
|
else if (payload.type === 'restorePlayerControl') {
|
||||||
this._enablePlayerControlStream.next();
|
this._enablePlayerControlStream.next();
|
||||||
}
|
}
|
||||||
else if (payload.type === 'displayBubble'){
|
else if (payload.type === 'displayBubble') {
|
||||||
this._displayBubbleStream.next();
|
this._displayBubbleStream.next();
|
||||||
}
|
}
|
||||||
else if (payload.type === 'removeBubble'){
|
else if (payload.type === 'removeBubble') {
|
||||||
this._removeBubbleStream.next();
|
this._removeBubbleStream.next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -134,7 +134,7 @@ class IframeListener {
|
|||||||
const iframe = document.createElement('iframe');
|
const iframe = document.createElement('iframe');
|
||||||
iframe.id = this.getIFrameId(scriptUrl);
|
iframe.id = this.getIFrameId(scriptUrl);
|
||||||
iframe.style.display = 'none';
|
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.
|
// 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');
|
iframe.sandbox.add('allow-scripts');
|
||||||
@ -158,8 +158,8 @@ class IframeListener {
|
|||||||
'\n' +
|
'\n' +
|
||||||
'<html lang="en">\n' +
|
'<html lang="en">\n' +
|
||||||
'<head>\n' +
|
'<head>\n' +
|
||||||
'<script src="'+window.location.protocol+'//'+window.location.host+'/iframe_api.js" ></script>\n' +
|
'<script src="' + window.location.protocol + '//' + window.location.host + '/iframe_api.js" ></script>\n' +
|
||||||
'<script src="'+scriptUrl+'" ></script>\n' +
|
'<script src="' + scriptUrl + '" ></script>\n' +
|
||||||
'</head>\n' +
|
'</head>\n' +
|
||||||
'</html>\n';
|
'</html>\n';
|
||||||
|
|
||||||
@ -176,14 +176,14 @@ class IframeListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getIFrameId(scriptUrl: string): string {
|
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 {
|
unregisterScript(scriptUrl: string): void {
|
||||||
const iFrameId = this.getIFrameId(scriptUrl);
|
const iFrameId = this.getIFrameId(scriptUrl);
|
||||||
const iframe = HtmlUtils.getElementByIdOrFail<HTMLIFrameElement>(iFrameId);
|
const iframe = HtmlUtils.getElementByIdOrFail<HTMLIFrameElement>(iFrameId);
|
||||||
if (!iframe) {
|
if (!iframe) {
|
||||||
throw new Error('Unknown iframe for script "'+scriptUrl+'"');
|
throw new Error('Unknown iframe for script "' + scriptUrl + '"');
|
||||||
}
|
}
|
||||||
this.unregisterIframe(iframe);
|
this.unregisterIframe(iframe);
|
||||||
iframe.remove();
|
iframe.remove();
|
||||||
@ -231,7 +231,7 @@ class IframeListener {
|
|||||||
/**
|
/**
|
||||||
* Sends the message... to all allowed iframes.
|
* Sends the message... to all allowed iframes.
|
||||||
*/
|
*/
|
||||||
private postMessage(message: IframeEvent) {
|
private postMessage(message: IframeResponseEvent<keyof IframeResponseEventMap>) {
|
||||||
for (const iframe of this.iframes) {
|
for (const iframe of this.iframes) {
|
||||||
iframe.contentWindow?.postMessage(message, '*');
|
iframe.contentWindow?.postMessage(message, '*');
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import {ChatEvent, isChatEvent} from "./Api/Events/ChatEvent";
|
import { ChatEvent } from "./Api/Events/ChatEvent";
|
||||||
import {isIframeEventWrapper} from "./Api/Events/IframeEvent";
|
import { isIframeResponseEventWrapper } from "./Api/Events/IframeEvent";
|
||||||
import {isUserInputChatEvent, UserInputChatEvent} from "./Api/Events/UserInputChatEvent";
|
import { isUserInputChatEvent, UserInputChatEvent } from "./Api/Events/UserInputChatEvent";
|
||||||
import {Subject} from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
import {EnterLeaveEvent, isEnterLeaveEvent} from "./Api/Events/EnterLeaveEvent";
|
import { EnterLeaveEvent, isEnterLeaveEvent } from "./Api/Events/EnterLeaveEvent";
|
||||||
import {OpenPopupEvent} from "./Api/Events/OpenPopupEvent";
|
import { OpenPopupEvent } from "./Api/Events/OpenPopupEvent";
|
||||||
import {isButtonClickedEvent} from "./Api/Events/ButtonClickedEvent";
|
import { isButtonClickedEvent } from "./Api/Events/ButtonClickedEvent";
|
||||||
import {ClosePopupEvent} from "./Api/Events/ClosePopupEvent";
|
import { ClosePopupEvent } from "./Api/Events/ClosePopupEvent";
|
||||||
import {OpenTabEvent} from "./Api/Events/OpenTabEvent";
|
import { OpenTabEvent } from "./Api/Events/OpenTabEvent";
|
||||||
import {GoToPageEvent} from "./Api/Events/GoToPageEvent";
|
import { GoToPageEvent } from "./Api/Events/GoToPageEvent";
|
||||||
import {OpenCoWebSiteEvent} from "./Api/Events/OpenCoWebSiteEvent";
|
import { OpenCoWebSiteEvent } from "./Api/Events/OpenCoWebSiteEvent";
|
||||||
|
|
||||||
interface WorkAdventureApi {
|
interface WorkAdventureApi {
|
||||||
sendChatMessage(message: string, author: string): void;
|
sendChatMessage(message: string, author: string): void;
|
||||||
@ -20,10 +20,10 @@ interface WorkAdventureApi {
|
|||||||
goToPage(url : string): void;
|
goToPage(url : string): void;
|
||||||
openCoWebSite(url : string): void;
|
openCoWebSite(url : string): void;
|
||||||
closeCoWebSite(): void;
|
closeCoWebSite(): void;
|
||||||
disablePlayerControl() : void;
|
disablePlayerControl(): void;
|
||||||
restorePlayerControl() : void;
|
restorePlayerControl(): void;
|
||||||
displayBubble() : void;
|
displayBubble(): void;
|
||||||
removeBubble() : void;
|
removeBubble(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@ -50,7 +50,7 @@ interface ButtonDescriptor {
|
|||||||
/**
|
/**
|
||||||
* The type of the button. Can be one of "normal", "primary", "success", "warning", "error", "disabled"
|
* 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
|
* Callback called if the button is pressed
|
||||||
*/
|
*/
|
||||||
@ -88,38 +88,38 @@ window.WA = {
|
|||||||
} as ChatEvent
|
} as ChatEvent
|
||||||
}, '*');
|
}, '*');
|
||||||
},
|
},
|
||||||
disablePlayerControl() : void {
|
disablePlayerControl(): void {
|
||||||
window.parent.postMessage({'type' : 'disablePlayerControl'},'*');
|
window.parent.postMessage({ 'type': 'disablePlayerControl' }, '*');
|
||||||
},
|
},
|
||||||
|
|
||||||
restorePlayerControl() : void {
|
restorePlayerControl(): void {
|
||||||
window.parent.postMessage({'type' : 'restorePlayerControl'},'*');
|
window.parent.postMessage({ 'type': 'restorePlayerControl' }, '*');
|
||||||
},
|
},
|
||||||
|
|
||||||
displayBubble() : void {
|
displayBubble(): void {
|
||||||
window.parent.postMessage({'type' : 'displayBubble'},'*');
|
window.parent.postMessage({ 'type': 'displayBubble' }, '*');
|
||||||
},
|
},
|
||||||
|
|
||||||
removeBubble() : void {
|
removeBubble(): void {
|
||||||
window.parent.postMessage({'type' : 'removeBubble'},'*');
|
window.parent.postMessage({ 'type': 'removeBubble' }, '*');
|
||||||
},
|
},
|
||||||
|
|
||||||
openTab(url : string) : void{
|
openTab(url: string): void {
|
||||||
window.parent.postMessage({
|
window.parent.postMessage({
|
||||||
"type" : 'openTab',
|
"type": 'openTab',
|
||||||
"data" : {
|
"data": {
|
||||||
url
|
url
|
||||||
} as OpenTabEvent
|
} as OpenTabEvent
|
||||||
},'*');
|
}, '*');
|
||||||
},
|
},
|
||||||
|
|
||||||
goToPage(url : string) : void{
|
goToPage(url: string): void {
|
||||||
window.parent.postMessage({
|
window.parent.postMessage({
|
||||||
"type" : 'goToPage',
|
"type": 'goToPage',
|
||||||
"data" : {
|
"data": {
|
||||||
url
|
url
|
||||||
} as GoToPageEvent
|
} as GoToPageEvent
|
||||||
},'*');
|
}, '*');
|
||||||
},
|
},
|
||||||
|
|
||||||
openCoWebSite(url : string) : void{
|
openCoWebSite(url : string) : void{
|
||||||
@ -128,13 +128,13 @@ window.WA = {
|
|||||||
"data" : {
|
"data" : {
|
||||||
url
|
url
|
||||||
} as OpenCoWebSiteEvent
|
} as OpenCoWebSiteEvent
|
||||||
},'*');
|
}, '*');
|
||||||
},
|
},
|
||||||
|
|
||||||
closeCoWebSite() : void{
|
closeCoWebSite(): void {
|
||||||
window.parent.postMessage({
|
window.parent.postMessage({
|
||||||
"type" : 'closeCoWebSite'
|
"type": 'closeCoWebSite'
|
||||||
},'*');
|
}, '*');
|
||||||
},
|
},
|
||||||
|
|
||||||
openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup {
|
openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup {
|
||||||
@ -205,9 +205,9 @@ window.addEventListener('message', message => {
|
|||||||
|
|
||||||
const payload = message.data;
|
const payload = message.data;
|
||||||
|
|
||||||
console.log(payload);
|
console.debug(payload);
|
||||||
|
|
||||||
if (isIframeEventWrapper(payload)) {
|
if (isIframeResponseEventWrapper(payload)) {
|
||||||
const payloadData = payload.data;
|
const payloadData = payload.data;
|
||||||
if (payload.type === 'userInputChat' && isUserInputChatEvent(payloadData)) {
|
if (payload.type === 'userInputChat' && isUserInputChatEvent(payloadData)) {
|
||||||
userInputChatStream.next(payloadData);
|
userInputChatStream.next(payloadData);
|
||||||
@ -219,7 +219,7 @@ window.addEventListener('message', message => {
|
|||||||
const callback = popupCallbacks.get(payloadData.popupId)?.get(payloadData.buttonId);
|
const callback = popupCallbacks.get(payloadData.popupId)?.get(payloadData.buttonId);
|
||||||
const popup = popups.get(payloadData.popupId);
|
const popup = popups.get(payloadData.popupId);
|
||||||
if (popup === undefined) {
|
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) {
|
if (callback) {
|
||||||
callback(popup);
|
callback(popup);
|
||||||
|
Loading…
Reference in New Issue
Block a user