Merge pull request #1258 from thecodingmachine/query-answer-mechanism
Add new "query/answer" utility functions for the scripting API
This commit is contained in:
commit
a7ced533c0
@ -81,7 +81,7 @@ WA.room.getCurrentRoom(): Promise<Room>
|
|||||||
```
|
```
|
||||||
Return a promise that resolves to a `Room` object with the following attributes :
|
Return a promise that resolves to a `Room` object with the following attributes :
|
||||||
* **id (string) :** ID of the current room
|
* **id (string) :** ID of the current room
|
||||||
* **map (ITiledMap) :** contains the JSON map file with the properties that were setted by the script if `setProperty` was called.
|
* **map (ITiledMap) :** contains the JSON map file with the properties that were set by the script if `setProperty` was called.
|
||||||
* **mapUrl (string) :** Url of the JSON map file
|
* **mapUrl (string) :** Url of the JSON map file
|
||||||
* **startLayer (string | null) :** Name of the layer where the current user started, only if different from `start` layer
|
* **startLayer (string | null) :** Name of the layer where the current user started, only if different from `start` layer
|
||||||
|
|
||||||
|
@ -23,6 +23,9 @@ export interface TypedMessageEvent<T> extends MessageEvent {
|
|||||||
data: T;
|
data: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List event types sent from an iFrame to WorkAdventure
|
||||||
|
*/
|
||||||
export type IframeEventMap = {
|
export type IframeEventMap = {
|
||||||
loadPage: LoadPageEvent;
|
loadPage: LoadPageEvent;
|
||||||
chat: ChatEvent;
|
chat: ChatEvent;
|
||||||
@ -62,7 +65,6 @@ export interface IframeResponseEventMap {
|
|||||||
enterEvent: EnterLeaveEvent;
|
enterEvent: EnterLeaveEvent;
|
||||||
leaveEvent: EnterLeaveEvent;
|
leaveEvent: EnterLeaveEvent;
|
||||||
buttonClickedEvent: ButtonClickedEvent;
|
buttonClickedEvent: ButtonClickedEvent;
|
||||||
gameState: GameStateEvent;
|
|
||||||
hasPlayerMoved: HasPlayerMovedEvent;
|
hasPlayerMoved: HasPlayerMovedEvent;
|
||||||
dataLayer: DataLayerEvent;
|
dataLayer: DataLayerEvent;
|
||||||
menuItemClicked: MenuItemClickedEvent;
|
menuItemClicked: MenuItemClickedEvent;
|
||||||
@ -76,3 +78,46 @@ export interface IframeResponseEvent<T extends keyof IframeResponseEventMap> {
|
|||||||
export const isIframeResponseEventWrapper = (event: {
|
export const isIframeResponseEventWrapper = (event: {
|
||||||
type?: string;
|
type?: string;
|
||||||
}): event is IframeResponseEvent<keyof IframeResponseEventMap> => typeof event.type === "string";
|
}): event is IframeResponseEvent<keyof IframeResponseEventMap> => typeof event.type === "string";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List event types sent from an iFrame to WorkAdventure that expect a unique answer from WorkAdventure along the type for the answer from WorkAdventure to the iFrame
|
||||||
|
*/
|
||||||
|
export type IframeQueryMap = {
|
||||||
|
getState: {
|
||||||
|
query: undefined,
|
||||||
|
answer: GameStateEvent
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IframeQuery<T extends keyof IframeQueryMap> {
|
||||||
|
type: T;
|
||||||
|
data: IframeQueryMap[T]['query'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IframeQueryWrapper<T extends keyof IframeQueryMap> {
|
||||||
|
id: number;
|
||||||
|
query: IframeQuery<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export const isIframeQuery = (event: any): event is IframeQuery<keyof IframeQueryMap> => typeof event.type === 'string';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export const isIframeQueryWrapper = (event: any): event is IframeQueryWrapper<keyof IframeQueryMap> => typeof event.id === 'number' && isIframeQuery(event.query);
|
||||||
|
|
||||||
|
export interface IframeAnswerEvent<T extends keyof IframeQueryMap> {
|
||||||
|
id: number;
|
||||||
|
type: T;
|
||||||
|
data: IframeQueryMap[T]['answer'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isIframeAnswerEvent = (event: { type?: string, id?: number }): event is IframeAnswerEvent<keyof IframeQueryMap> => typeof event.type === 'string' && typeof event.id === 'number';
|
||||||
|
|
||||||
|
export interface IframeErrorAnswerEvent {
|
||||||
|
id: number;
|
||||||
|
type: keyof IframeQueryMap;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isIframeErrorAnswerEvent = (event: { type?: string, id?: number, error?: string }): event is IframeErrorAnswerEvent => typeof event.type === 'string' && typeof event.id === 'number' && typeof event.error === 'string';
|
||||||
|
@ -10,11 +10,13 @@ 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 {
|
import {
|
||||||
|
IframeErrorAnswerEvent,
|
||||||
IframeEvent,
|
IframeEvent,
|
||||||
IframeEventMap,
|
IframeEventMap, IframeQueryMap,
|
||||||
IframeResponseEvent,
|
IframeResponseEvent,
|
||||||
IframeResponseEventMap,
|
IframeResponseEventMap,
|
||||||
isIframeEventWrapper,
|
isIframeEventWrapper,
|
||||||
|
isIframeQueryWrapper,
|
||||||
TypedMessageEvent,
|
TypedMessageEvent,
|
||||||
} from "./Events/IframeEvent";
|
} from "./Events/IframeEvent";
|
||||||
import type { UserInputChatEvent } from "./Events/UserInputChatEvent";
|
import type { UserInputChatEvent } from "./Events/UserInputChatEvent";
|
||||||
@ -31,6 +33,8 @@ import { isLoadPageEvent } from "./Events/LoadPageEvent";
|
|||||||
import { handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent } from "./Events/ui/MenuItemRegisterEvent";
|
import { handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent } from "./Events/ui/MenuItemRegisterEvent";
|
||||||
import { SetTilesEvent, isSetTilesEvent } from "./Events/SetTilesEvent";
|
import { SetTilesEvent, isSetTilesEvent } from "./Events/SetTilesEvent";
|
||||||
|
|
||||||
|
type AnswererCallback<T extends keyof IframeQueryMap> = (query: IframeQueryMap[T]['query']) => IframeQueryMap[T]['answer']|Promise<IframeQueryMap[T]['answer']>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listens to messages from iframes and turn those messages into easy to use observables.
|
* Listens to messages from iframes and turn those messages into easy to use observables.
|
||||||
* Also allows to send messages to those iframes.
|
* Also allows to send messages to those iframes.
|
||||||
@ -81,9 +85,6 @@ class IframeListener {
|
|||||||
private readonly _setPropertyStream: Subject<SetPropertyEvent> = new Subject();
|
private readonly _setPropertyStream: Subject<SetPropertyEvent> = new Subject();
|
||||||
public readonly setPropertyStream = this._setPropertyStream.asObservable();
|
public readonly setPropertyStream = this._setPropertyStream.asObservable();
|
||||||
|
|
||||||
private readonly _gameStateStream: Subject<void> = new Subject();
|
|
||||||
public readonly gameStateStream = this._gameStateStream.asObservable();
|
|
||||||
|
|
||||||
private readonly _dataLayerChangeStream: Subject<void> = new Subject();
|
private readonly _dataLayerChangeStream: Subject<void> = new Subject();
|
||||||
public readonly dataLayerChangeStream = this._dataLayerChangeStream.asObservable();
|
public readonly dataLayerChangeStream = this._dataLayerChangeStream.asObservable();
|
||||||
|
|
||||||
@ -110,6 +111,10 @@ class IframeListener {
|
|||||||
private readonly scripts = new Map<string, HTMLIFrameElement>();
|
private readonly scripts = new Map<string, HTMLIFrameElement>();
|
||||||
private sendPlayerMove: boolean = false;
|
private sendPlayerMove: boolean = false;
|
||||||
|
|
||||||
|
private answerers: {
|
||||||
|
[key in keyof IframeQueryMap]?: AnswererCallback<key>
|
||||||
|
} = {};
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
"message",
|
"message",
|
||||||
@ -119,7 +124,7 @@ class IframeListener {
|
|||||||
// 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).
|
||||||
let foundSrc: string | undefined;
|
let foundSrc: string | undefined;
|
||||||
|
|
||||||
let iframe: HTMLIFrameElement;
|
let iframe: HTMLIFrameElement | undefined;
|
||||||
for (iframe of this.iframes) {
|
for (iframe of this.iframes) {
|
||||||
if (iframe.contentWindow === message.source) {
|
if (iframe.contentWindow === message.source) {
|
||||||
foundSrc = iframe.src;
|
foundSrc = iframe.src;
|
||||||
@ -129,7 +134,7 @@ class IframeListener {
|
|||||||
|
|
||||||
const payload = message.data;
|
const payload = message.data;
|
||||||
|
|
||||||
if (foundSrc === undefined) {
|
if (foundSrc === undefined || iframe === undefined) {
|
||||||
if (isIframeEventWrapper(payload)) {
|
if (isIframeEventWrapper(payload)) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"It seems an iFrame is trying to communicate with WorkAdventure but was not explicitly granted the permission to do so. " +
|
"It seems an iFrame is trying to communicate with WorkAdventure but was not explicitly granted the permission to do so. " +
|
||||||
@ -143,65 +148,101 @@ class IframeListener {
|
|||||||
|
|
||||||
foundSrc = this.getBaseUrl(foundSrc, message.source);
|
foundSrc = this.getBaseUrl(foundSrc, message.source);
|
||||||
|
|
||||||
if (isIframeEventWrapper(payload)) {
|
if (isIframeQueryWrapper(payload)) {
|
||||||
if (payload.type === "showLayer" && isLayerEvent(payload.data)) {
|
const queryId = payload.id;
|
||||||
this._showLayerStream.next(payload.data);
|
const query = payload.query;
|
||||||
} else if (payload.type === "hideLayer" && isLayerEvent(payload.data)) {
|
|
||||||
this._hideLayerStream.next(payload.data);
|
const answerer = this.answerers[query.type];
|
||||||
} else if (payload.type === "setProperty" && isSetPropertyEvent(payload.data)) {
|
if (answerer === undefined) {
|
||||||
this._setPropertyStream.next(payload.data);
|
const errorMsg = 'The iFrame sent a message of type "'+query.type+'" but there is no service configured to answer these messages.';
|
||||||
} else if (payload.type === "chat" && isChatEvent(payload.data)) {
|
console.error(errorMsg);
|
||||||
this._chatStream.next(payload.data);
|
iframe.contentWindow?.postMessage({
|
||||||
} else if (payload.type === "openPopup" && isOpenPopupEvent(payload.data)) {
|
id: queryId,
|
||||||
this._openPopupStream.next(payload.data);
|
type: query.type,
|
||||||
} else if (payload.type === "closePopup" && isClosePopupEvent(payload.data)) {
|
error: errorMsg
|
||||||
this._closePopupStream.next(payload.data);
|
} as IframeErrorAnswerEvent, '*');
|
||||||
} else if (payload.type === "openTab" && isOpenTabEvent(payload.data)) {
|
return;
|
||||||
scriptUtils.openTab(payload.data.url);
|
}
|
||||||
} else if (payload.type === "goToPage" && isGoToPageEvent(payload.data)) {
|
|
||||||
scriptUtils.goToPage(payload.data.url);
|
Promise.resolve(answerer(query.data)).then((value) => {
|
||||||
} else if (payload.type === "loadPage" && isLoadPageEvent(payload.data)) {
|
iframe?.contentWindow?.postMessage({
|
||||||
this._loadPageStream.next(payload.data.url);
|
id: queryId,
|
||||||
} else if (payload.type === "playSound" && isPlaySoundEvent(payload.data)) {
|
type: query.type,
|
||||||
this._playSoundStream.next(payload.data);
|
data: value
|
||||||
} else if (payload.type === "stopSound" && isStopSoundEvent(payload.data)) {
|
}, '*');
|
||||||
this._stopSoundStream.next(payload.data);
|
}).catch(reason => {
|
||||||
} else if (payload.type === "loadSound" && isLoadSoundEvent(payload.data)) {
|
console.error('An error occurred while responding to an iFrame query.', reason);
|
||||||
this._loadSoundStream.next(payload.data);
|
let reasonMsg: string;
|
||||||
} else if (payload.type === "openCoWebSite" && isOpenCoWebsite(payload.data)) {
|
if (reason instanceof Error) {
|
||||||
scriptUtils.openCoWebsite(
|
reasonMsg = reason.message;
|
||||||
payload.data.url,
|
} else {
|
||||||
foundSrc,
|
reasonMsg = reason.toString();
|
||||||
payload.data.allowApi,
|
}
|
||||||
payload.data.allowPolicy
|
|
||||||
);
|
iframe?.contentWindow?.postMessage({
|
||||||
} else if (payload.type === "closeCoWebSite") {
|
id: queryId,
|
||||||
scriptUtils.closeCoWebSite();
|
type: query.type,
|
||||||
} else if (payload.type === "disablePlayerControls") {
|
error: reasonMsg
|
||||||
this._disablePlayerControlStream.next();
|
} as IframeErrorAnswerEvent, '*');
|
||||||
} else if (payload.type === "restorePlayerControls") {
|
});
|
||||||
this._enablePlayerControlStream.next();
|
|
||||||
} else if (payload.type === "displayBubble") {
|
} else if (isIframeEventWrapper(payload)) {
|
||||||
this._displayBubbleStream.next();
|
if (payload.type === "showLayer" && isLayerEvent(payload.data)) {
|
||||||
} else if (payload.type === "removeBubble") {
|
this._showLayerStream.next(payload.data);
|
||||||
this._removeBubbleStream.next();
|
} else if (payload.type === "hideLayer" && isLayerEvent(payload.data)) {
|
||||||
} else if (payload.type == "getState") {
|
this._hideLayerStream.next(payload.data);
|
||||||
this._gameStateStream.next();
|
} else if (payload.type === "setProperty" && isSetPropertyEvent(payload.data)) {
|
||||||
} else if (payload.type == "onPlayerMove") {
|
this._setPropertyStream.next(payload.data);
|
||||||
this.sendPlayerMove = true;
|
} else if (payload.type === "chat" && isChatEvent(payload.data)) {
|
||||||
} else if (payload.type == "getDataLayer") {
|
this._chatStream.next(payload.data);
|
||||||
this._dataLayerChangeStream.next();
|
} else if (payload.type === "openPopup" && isOpenPopupEvent(payload.data)) {
|
||||||
} else if (isMenuItemRegisterIframeEvent(payload)) {
|
this._openPopupStream.next(payload.data);
|
||||||
const data = payload.data.menutItem;
|
} else if (payload.type === "closePopup" && isClosePopupEvent(payload.data)) {
|
||||||
// @ts-ignore
|
this._closePopupStream.next(payload.data);
|
||||||
this.iframeCloseCallbacks.get(iframe).push(() => {
|
} else if (payload.type === "openTab" && isOpenTabEvent(payload.data)) {
|
||||||
this._unregisterMenuCommandStream.next(data);
|
scriptUtils.openTab(payload.data.url);
|
||||||
});
|
} else if (payload.type === "goToPage" && isGoToPageEvent(payload.data)) {
|
||||||
handleMenuItemRegistrationEvent(payload.data);
|
scriptUtils.goToPage(payload.data.url);
|
||||||
} else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) {
|
} else if (payload.type === "loadPage" && isLoadPageEvent(payload.data)) {
|
||||||
this._setTilesStream.next(payload.data);
|
this._loadPageStream.next(payload.data.url);
|
||||||
|
} else if (payload.type === "playSound" && isPlaySoundEvent(payload.data)) {
|
||||||
|
this._playSoundStream.next(payload.data);
|
||||||
|
} else if (payload.type === "stopSound" && isStopSoundEvent(payload.data)) {
|
||||||
|
this._stopSoundStream.next(payload.data);
|
||||||
|
} else if (payload.type === "loadSound" && isLoadSoundEvent(payload.data)) {
|
||||||
|
this._loadSoundStream.next(payload.data);
|
||||||
|
} else if (payload.type === "openCoWebSite" && isOpenCoWebsite(payload.data)) {
|
||||||
|
scriptUtils.openCoWebsite(
|
||||||
|
payload.data.url,
|
||||||
|
foundSrc,
|
||||||
|
payload.data.allowApi,
|
||||||
|
payload.data.allowPolicy
|
||||||
|
);
|
||||||
|
} else if (payload.type === "closeCoWebSite") {
|
||||||
|
scriptUtils.closeCoWebSite();
|
||||||
|
} else if (payload.type === "disablePlayerControls") {
|
||||||
|
this._disablePlayerControlStream.next();
|
||||||
|
} else if (payload.type === "restorePlayerControls") {
|
||||||
|
this._enablePlayerControlStream.next();
|
||||||
|
} else if (payload.type === "displayBubble") {
|
||||||
|
this._displayBubbleStream.next();
|
||||||
|
} else if (payload.type === "removeBubble") {
|
||||||
|
this._removeBubbleStream.next();
|
||||||
|
} else if (payload.type == "onPlayerMove") {
|
||||||
|
this.sendPlayerMove = true;
|
||||||
|
} else if (payload.type == "getDataLayer") {
|
||||||
|
this._dataLayerChangeStream.next();
|
||||||
|
} else if (isMenuItemRegisterIframeEvent(payload)) {
|
||||||
|
const data = payload.data.menutItem;
|
||||||
|
// @ts-ignore
|
||||||
|
this.iframeCloseCallbacks.get(iframe).push(() => {
|
||||||
|
this._unregisterMenuCommandStream.next(data);
|
||||||
|
});
|
||||||
|
handleMenuItemRegistrationEvent(payload.data);
|
||||||
|
} else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) {
|
||||||
|
this._setTilesStream.next(payload.data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
@ -214,13 +255,6 @@ class IframeListener {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sendGameStateEvent(gameStateEvent: GameStateEvent) {
|
|
||||||
this.postMessage({
|
|
||||||
type: "gameState",
|
|
||||||
data: gameStateEvent,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows the passed iFrame to send/receive messages via the API.
|
* Allows the passed iFrame to send/receive messages via the API.
|
||||||
*/
|
*/
|
||||||
@ -368,6 +402,22 @@ class IframeListener {
|
|||||||
iframe.contentWindow?.postMessage(message, "*");
|
iframe.contentWindow?.postMessage(message, "*");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a callback that can be used to respond to some query (as defined in the IframeQueryMap type).
|
||||||
|
*
|
||||||
|
* Important! There can be only one "answerer" so registering a new one will unregister the old one.
|
||||||
|
*
|
||||||
|
* @param key The "type" of the query we are answering
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
|
public registerAnswerer<T extends keyof IframeQueryMap>(key: T, callback: (query: IframeQueryMap[T]['query']) => IframeQueryMap[T]['answer']|Promise<IframeQueryMap[T]['answer']> ): void {
|
||||||
|
this.answerers[key] = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unregisterAnswerer(key: keyof IframeQueryMap): void {
|
||||||
|
delete this.answerers[key];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const iframeListener = new IframeListener();
|
export const iframeListener = new IframeListener();
|
||||||
|
@ -1,9 +1,40 @@
|
|||||||
import type * as tg from "generic-type-guard";
|
import type * as tg from "generic-type-guard";
|
||||||
import type { IframeEvent, IframeEventMap, IframeResponseEventMap } from '../Events/IframeEvent';
|
import type {
|
||||||
|
IframeEvent,
|
||||||
|
IframeEventMap, IframeQuery,
|
||||||
|
IframeQueryMap,
|
||||||
|
IframeResponseEventMap
|
||||||
|
} from '../Events/IframeEvent';
|
||||||
|
import type {IframeQueryWrapper} from "../Events/IframeEvent";
|
||||||
|
|
||||||
export function sendToWorkadventure(content: IframeEvent<keyof IframeEventMap>) {
|
export function sendToWorkadventure(content: IframeEvent<keyof IframeEventMap>) {
|
||||||
window.parent.postMessage(content, "*")
|
window.parent.postMessage(content, "*")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let queryNumber = 0;
|
||||||
|
|
||||||
|
export const answerPromises = new Map<number, {
|
||||||
|
resolve: (value: (IframeQueryMap[keyof IframeQueryMap]['answer'] | PromiseLike<IframeQueryMap[keyof IframeQueryMap]['answer']>)) => void,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
reject: (reason?: any) => void
|
||||||
|
}>();
|
||||||
|
|
||||||
|
export function queryWorkadventure<T extends keyof IframeQueryMap>(content: IframeQuery<T>): Promise<IframeQueryMap[T]['answer']> {
|
||||||
|
return new Promise<IframeQueryMap[T]['answer']>((resolve, reject) => {
|
||||||
|
window.parent.postMessage({
|
||||||
|
id: queryNumber,
|
||||||
|
query: content
|
||||||
|
} as IframeQueryWrapper<T>, "*");
|
||||||
|
|
||||||
|
answerPromises.set(queryNumber, {
|
||||||
|
resolve,
|
||||||
|
reject
|
||||||
|
});
|
||||||
|
|
||||||
|
queryNumber++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
type GuardedType<Guard extends tg.TypeGuard<unknown>> = Guard extends tg.TypeGuard<infer T> ? T : never
|
type GuardedType<Guard extends tg.TypeGuard<unknown>> = Guard extends tg.TypeGuard<infer T> ? T : never
|
||||||
|
|
||||||
export interface IframeCallback<Key extends keyof IframeResponseEventMap, T = IframeResponseEventMap[Key], Guard = tg.TypeGuard<T>> {
|
export interface IframeCallback<Key extends keyof IframeResponseEventMap, T = IframeResponseEventMap[Key], Guard = tg.TypeGuard<T>> {
|
||||||
|
@ -4,7 +4,7 @@ import { isDataLayerEvent } from "../Events/DataLayerEvent";
|
|||||||
import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent";
|
import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent";
|
||||||
import { isGameStateEvent } from "../Events/GameStateEvent";
|
import { isGameStateEvent } from "../Events/GameStateEvent";
|
||||||
|
|
||||||
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
|
import {IframeApiContribution, queryWorkadventure, sendToWorkadventure} from "./IframeApiContribution";
|
||||||
import { apiCallback } from "./registeredCallbacks";
|
import { apiCallback } from "./registeredCallbacks";
|
||||||
|
|
||||||
import type { ITiledMap } from "../../Phaser/Map/ITiledMap";
|
import type { ITiledMap } from "../../Phaser/Map/ITiledMap";
|
||||||
@ -16,7 +16,7 @@ const leaveStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subj
|
|||||||
const dataLayerResolver = new Subject<DataLayerEvent>();
|
const dataLayerResolver = new Subject<DataLayerEvent>();
|
||||||
const stateResolvers = new Subject<GameStateEvent>();
|
const stateResolvers = new Subject<GameStateEvent>();
|
||||||
|
|
||||||
let immutableData: GameStateEvent;
|
let immutableDataPromise: Promise<GameStateEvent> | undefined = undefined;
|
||||||
|
|
||||||
interface Room {
|
interface Room {
|
||||||
id: string;
|
id: string;
|
||||||
@ -39,14 +39,10 @@ interface TileDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getGameState(): Promise<GameStateEvent> {
|
function getGameState(): Promise<GameStateEvent> {
|
||||||
if (immutableData) {
|
if (immutableDataPromise === undefined) {
|
||||||
return Promise.resolve(immutableData);
|
immutableDataPromise = queryWorkadventure({ type: "getState", data: undefined });
|
||||||
} else {
|
|
||||||
return new Promise<GameStateEvent>((resolver, thrower) => {
|
|
||||||
stateResolvers.subscribe(resolver);
|
|
||||||
sendToWorkadventure({ type: "getState", data: null });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
return immutableDataPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDataLayer(): Promise<DataLayerEvent> {
|
function getDataLayer(): Promise<DataLayerEvent> {
|
||||||
@ -72,13 +68,6 @@ export class WorkadventureRoomCommands extends IframeApiContribution<Workadventu
|
|||||||
leaveStreams.get(payloadData.name)?.next();
|
leaveStreams.get(payloadData.name)?.next();
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
apiCallback({
|
|
||||||
type: "gameState",
|
|
||||||
typeChecker: isGameStateEvent,
|
|
||||||
callback: (payloadData) => {
|
|
||||||
stateResolvers.next(payloadData);
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
apiCallback({
|
apiCallback({
|
||||||
type: "dataLayer",
|
type: "dataLayer",
|
||||||
typeChecker: isDataLayerEvent,
|
typeChecker: isDataLayerEvent,
|
||||||
|
@ -44,7 +44,6 @@ export class TextUtils {
|
|||||||
options.align = object.text.halign;
|
options.align = object.text.halign;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.warn(options);
|
|
||||||
const textElem = scene.add.text(object.x, object.y, object.text.text, options);
|
const textElem = scene.add.text(object.x, object.y, object.text.text, options);
|
||||||
textElem.setAngle(object.rotation);
|
textElem.setAngle(object.rotation);
|
||||||
}
|
}
|
||||||
|
@ -1044,18 +1044,16 @@ ${escapedMessage}
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
this.iframeSubscriptionList.push(
|
iframeListener.registerAnswerer('getState', () => {
|
||||||
iframeListener.gameStateStream.subscribe(() => {
|
return {
|
||||||
iframeListener.sendGameStateEvent({
|
mapUrl: this.MapUrlFile,
|
||||||
mapUrl: this.MapUrlFile,
|
startLayerName: this.startPositionCalculator.startLayerName,
|
||||||
startLayerName: this.startPositionCalculator.startLayerName,
|
uuid: localUserStore.getLocalUser()?.uuid,
|
||||||
uuid: localUserStore.getLocalUser()?.uuid,
|
nickname: localUserStore.getName(),
|
||||||
nickname: localUserStore.getName(),
|
roomId: this.RoomId,
|
||||||
roomId: this.RoomId,
|
tags: this.connection ? this.connection.getAllTags() : [],
|
||||||
tags: this.connection ? this.connection.getAllTags() : [],
|
};
|
||||||
});
|
});
|
||||||
})
|
|
||||||
);
|
|
||||||
this.iframeSubscriptionList.push(
|
this.iframeSubscriptionList.push(
|
||||||
iframeListener.setTilesStream.subscribe((eventTiles) => {
|
iframeListener.setTilesStream.subscribe((eventTiles) => {
|
||||||
for (const eventTile of eventTiles) {
|
for (const eventTile of eventTiles) {
|
||||||
@ -1149,6 +1147,7 @@ ${escapedMessage}
|
|||||||
this.emoteManager.destroy();
|
this.emoteManager.destroy();
|
||||||
this.peerStoreUnsubscribe();
|
this.peerStoreUnsubscribe();
|
||||||
this.biggestAvailableAreaStoreUnsubscribe();
|
this.biggestAvailableAreaStoreUnsubscribe();
|
||||||
|
iframeListener.unregisterAnswerer('getState');
|
||||||
|
|
||||||
mediaManager.hideGameOverlay();
|
mediaManager.hideGameOverlay();
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { registeredCallbacks } from "./Api/iframe/registeredCallbacks";
|
import { registeredCallbacks } from "./Api/iframe/registeredCallbacks";
|
||||||
import {
|
import {
|
||||||
IframeResponseEvent,
|
IframeResponseEvent,
|
||||||
IframeResponseEventMap,
|
IframeResponseEventMap, isIframeAnswerEvent, isIframeErrorAnswerEvent,
|
||||||
isIframeResponseEventWrapper,
|
isIframeResponseEventWrapper,
|
||||||
TypedMessageEvent,
|
TypedMessageEvent,
|
||||||
} from "./Api/Events/IframeEvent";
|
} from "./Api/Events/IframeEvent";
|
||||||
@ -16,7 +16,7 @@ import player from "./Api/iframe/player";
|
|||||||
import type { ButtonDescriptor } from "./Api/iframe/Ui/ButtonDescriptor";
|
import type { ButtonDescriptor } from "./Api/iframe/Ui/ButtonDescriptor";
|
||||||
import type { Popup } from "./Api/iframe/Ui/Popup";
|
import type { Popup } from "./Api/iframe/Ui/Popup";
|
||||||
import type { Sound } from "./Api/iframe/Sound/Sound";
|
import type { Sound } from "./Api/iframe/Sound/Sound";
|
||||||
import { sendToWorkadventure } from "./Api/iframe/IframeApiContribution";
|
import { answerPromises, sendToWorkadventure} from "./Api/iframe/IframeApiContribution";
|
||||||
|
|
||||||
const wa = {
|
const wa = {
|
||||||
ui,
|
ui,
|
||||||
@ -164,16 +164,38 @@ declare global {
|
|||||||
window.WA = wa;
|
window.WA = wa;
|
||||||
|
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
"message",
|
"message", <T extends keyof IframeResponseEventMap>(message: TypedMessageEvent<IframeResponseEvent<T>>) => {
|
||||||
<T extends keyof IframeResponseEventMap>(message: TypedMessageEvent<IframeResponseEvent<T>>) => {
|
if (message.source !== window.parent) {
|
||||||
if (message.source !== window.parent) {
|
return; // Skip message in this event listener
|
||||||
return; // Skip message in this event listener
|
}
|
||||||
}
|
const payload = message.data;
|
||||||
const payload = message.data;
|
|
||||||
console.debug(payload);
|
|
||||||
|
|
||||||
if (isIframeResponseEventWrapper(payload)) {
|
console.debug(payload);
|
||||||
const payloadData = payload.data;
|
|
||||||
|
if (isIframeAnswerEvent(payload)) {
|
||||||
|
const queryId = payload.id;
|
||||||
|
const payloadData = payload.data;
|
||||||
|
|
||||||
|
const resolver = answerPromises.get(queryId);
|
||||||
|
if (resolver === undefined) {
|
||||||
|
throw new Error('In Iframe API, got an answer for a question that we have no track of.');
|
||||||
|
}
|
||||||
|
resolver.resolve(payloadData);
|
||||||
|
|
||||||
|
answerPromises.delete(queryId);
|
||||||
|
} else if (isIframeErrorAnswerEvent(payload)) {
|
||||||
|
const queryId = payload.id;
|
||||||
|
const payloadError = payload.error;
|
||||||
|
|
||||||
|
const resolver = answerPromises.get(queryId);
|
||||||
|
if (resolver === undefined) {
|
||||||
|
throw new Error('In Iframe API, got an error answer for a question that we have no track of.');
|
||||||
|
}
|
||||||
|
resolver.reject(payloadError);
|
||||||
|
|
||||||
|
answerPromises.delete(queryId);
|
||||||
|
} else if (isIframeResponseEventWrapper(payload)) {
|
||||||
|
const payloadData = payload.data;
|
||||||
|
|
||||||
const callback = registeredCallbacks[payload.type] as IframeCallback<T> | undefined;
|
const callback = registeredCallbacks[payload.type] as IframeCallback<T> | undefined;
|
||||||
if (callback?.typeChecker(payloadData)) {
|
if (callback?.typeChecker(payloadData)) {
|
||||||
|
Loading…
Reference in New Issue
Block a user