From 5b4a72ea1fa19a5600bc6ccb0f02d343746c2317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 2 Jul 2021 16:41:24 +0200 Subject: [PATCH 1/2] Add new "query/answer" utility functions for the scripting API So far, the scripting API was using events to communicate between WA and the iFrame. But often, the scripting API might actually want to "ask" WA a question and wait for an answer. We dealt with this by using 2 unrelated events (in a mostly painful way). This commit adds a "queryWorkadventure" utility function in the iFrame API that allows us to send a query, and to wait for an answer. The query and answer events have a unique ID to be sure the answer matches the correct query. On the WA side, a new `IframeListener.registerAnswerer` method can be used to register a possible answer. --- front/src/Api/Events/IframeEvent.ts | 47 ++++- front/src/Api/IframeListener.ts | 192 +++++++++++------- front/src/Api/iframe/IframeApiContribution.ts | 33 ++- front/src/Api/iframe/room.ts | 21 +- front/src/Phaser/Components/TextUtils.ts | 1 - front/src/Phaser/Game/GameScene.ts | 23 +-- front/src/iframe_api.ts | 44 +++- 7 files changed, 248 insertions(+), 113 deletions(-) diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 67775c03..fc3384f8 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -23,6 +23,9 @@ export interface TypedMessageEvent extends MessageEvent { data: T; } +/** + * List event types sent from an iFrame to WorkAdventure + */ export type IframeEventMap = { loadPage: LoadPageEvent; chat: ChatEvent; @@ -62,7 +65,6 @@ export interface IframeResponseEventMap { enterEvent: EnterLeaveEvent; leaveEvent: EnterLeaveEvent; buttonClickedEvent: ButtonClickedEvent; - gameState: GameStateEvent; hasPlayerMoved: HasPlayerMovedEvent; dataLayer: DataLayerEvent; menuItemClicked: MenuItemClickedEvent; @@ -76,3 +78,46 @@ export interface IframeResponseEvent { export const isIframeResponseEventWrapper = (event: { type?: string; }): event is IframeResponseEvent => 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 { + type: T; + data: IframeQueryMap[T]['query']; +} + +export interface IframeQueryWrapper { + id: number; + query: IframeQuery; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const isIframeQuery = (event: any): event is IframeQuery => typeof event.type === 'string'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const isIframeQueryWrapper = (event: any): event is IframeQueryWrapper => typeof event.id === 'number' && isIframeQuery(event.query); + +export interface IframeAnswerEvent { + id: number; + type: T; + data: IframeQueryMap[T]['answer']; +} + +export const isIframeAnswerEvent = (event: { type?: string, id?: number }): event is IframeAnswerEvent => 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'; diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index b97fc567..334535a5 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -10,11 +10,13 @@ import { scriptUtils } from "./ScriptUtils"; import { GoToPageEvent, isGoToPageEvent } from "./Events/GoToPageEvent"; import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent"; import { + IframeErrorAnswerEvent, IframeEvent, - IframeEventMap, + IframeEventMap, IframeQueryMap, IframeResponseEvent, IframeResponseEventMap, isIframeEventWrapper, + isIframeQueryWrapper, TypedMessageEvent, } from "./Events/IframeEvent"; import type { UserInputChatEvent } from "./Events/UserInputChatEvent"; @@ -31,6 +33,8 @@ import { isLoadPageEvent } from "./Events/LoadPageEvent"; import { handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent } from "./Events/ui/MenuItemRegisterEvent"; import { SetTilesEvent, isSetTilesEvent } from "./Events/SetTilesEvent"; +type AnswererCallback = (query: IframeQueryMap[T]['query']) => Promise; + /** * Listens to messages from iframes and turn those messages into easy to use observables. * Also allows to send messages to those iframes. @@ -81,9 +85,6 @@ class IframeListener { private readonly _setPropertyStream: Subject = new Subject(); public readonly setPropertyStream = this._setPropertyStream.asObservable(); - private readonly _gameStateStream: Subject = new Subject(); - public readonly gameStateStream = this._gameStateStream.asObservable(); - private readonly _dataLayerChangeStream: Subject = new Subject(); public readonly dataLayerChangeStream = this._dataLayerChangeStream.asObservable(); @@ -110,6 +111,10 @@ class IframeListener { private readonly scripts = new Map(); private sendPlayerMove: boolean = false; + private answerers: { + [key in keyof IframeQueryMap]?: AnswererCallback + } = {}; + init() { window.addEventListener( "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). let foundSrc: string | undefined; - let iframe: HTMLIFrameElement; + let iframe: HTMLIFrameElement | undefined; for (iframe of this.iframes) { if (iframe.contentWindow === message.source) { foundSrc = iframe.src; @@ -129,7 +134,7 @@ class IframeListener { const payload = message.data; - if (foundSrc === undefined) { + if (foundSrc === undefined || iframe === undefined) { if (isIframeEventWrapper(payload)) { console.warn( "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); - if (isIframeEventWrapper(payload)) { - if (payload.type === "showLayer" && isLayerEvent(payload.data)) { - this._showLayerStream.next(payload.data); - } else if (payload.type === "hideLayer" && isLayerEvent(payload.data)) { - this._hideLayerStream.next(payload.data); - } else if (payload.type === "setProperty" && isSetPropertyEvent(payload.data)) { - this._setPropertyStream.next(payload.data); - } else if (payload.type === "chat" && isChatEvent(payload.data)) { - this._chatStream.next(payload.data); - } else if (payload.type === "openPopup" && isOpenPopupEvent(payload.data)) { - this._openPopupStream.next(payload.data); - } else if (payload.type === "closePopup" && isClosePopupEvent(payload.data)) { - this._closePopupStream.next(payload.data); - } else if (payload.type === "openTab" && isOpenTabEvent(payload.data)) { - scriptUtils.openTab(payload.data.url); - } else if (payload.type === "goToPage" && isGoToPageEvent(payload.data)) { - scriptUtils.goToPage(payload.data.url); - } else if (payload.type === "loadPage" && isLoadPageEvent(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 == "getState") { - this._gameStateStream.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); + if (isIframeQueryWrapper(payload)) { + const queryId = payload.id; + const query = payload.query; + + const answerer = this.answerers[query.type]; + if (answerer === undefined) { + const errorMsg = 'The iFrame sent a message of type "'+query.type+'" but there is no service configured to answer these messages.'; + console.error(errorMsg); + iframe.contentWindow?.postMessage({ + id: queryId, + type: query.type, + error: errorMsg + } as IframeErrorAnswerEvent, '*'); + return; + } + + answerer(query.data).then((value) => { + iframe?.contentWindow?.postMessage({ + id: queryId, + type: query.type, + data: value + }, '*'); + }).catch(reason => { + console.error('An error occurred while responding to an iFrame query.', reason); + let reasonMsg: string; + if (reason instanceof Error) { + reasonMsg = reason.message; + } else { + reasonMsg = reason.toString(); + } + + iframe?.contentWindow?.postMessage({ + id: queryId, + type: query.type, + error: reasonMsg + } as IframeErrorAnswerEvent, '*'); + }); + + } else if (isIframeEventWrapper(payload)) { + if (payload.type === "showLayer" && isLayerEvent(payload.data)) { + this._showLayerStream.next(payload.data); + } else if (payload.type === "hideLayer" && isLayerEvent(payload.data)) { + this._hideLayerStream.next(payload.data); + } else if (payload.type === "setProperty" && isSetPropertyEvent(payload.data)) { + this._setPropertyStream.next(payload.data); + } else if (payload.type === "chat" && isChatEvent(payload.data)) { + this._chatStream.next(payload.data); + } else if (payload.type === "openPopup" && isOpenPopupEvent(payload.data)) { + this._openPopupStream.next(payload.data); + } else if (payload.type === "closePopup" && isClosePopupEvent(payload.data)) { + this._closePopupStream.next(payload.data); + } else if (payload.type === "openTab" && isOpenTabEvent(payload.data)) { + scriptUtils.openTab(payload.data.url); + } else if (payload.type === "goToPage" && isGoToPageEvent(payload.data)) { + scriptUtils.goToPage(payload.data.url); + } else if (payload.type === "loadPage" && isLoadPageEvent(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 ); @@ -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. */ @@ -368,6 +402,22 @@ class IframeListener { 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(key: T, callback: (query: IframeQueryMap[T]['query']) => Promise ): void { + this.answerers[key] = callback; + } + + public unregisterAnswerer(key: keyof IframeQueryMap): void { + delete this.answerers[key]; + } } export const iframeListener = new IframeListener(); diff --git a/front/src/Api/iframe/IframeApiContribution.ts b/front/src/Api/iframe/IframeApiContribution.ts index f3b25999..e4ba089e 100644 --- a/front/src/Api/iframe/IframeApiContribution.ts +++ b/front/src/Api/iframe/IframeApiContribution.ts @@ -1,9 +1,40 @@ 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) { window.parent.postMessage(content, "*") } + +let queryNumber = 0; + +export const answerPromises = new Map)) => void, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + reject: (reason?: any) => void +}>(); + +export function queryWorkadventure(content: IframeQuery): Promise { + return new Promise((resolve, reject) => { + window.parent.postMessage({ + id: queryNumber, + query: content + } as IframeQueryWrapper, "*"); + + answerPromises.set(queryNumber, { + resolve, + reject + }); + + queryNumber++; + }); +} + type GuardedType> = Guard extends tg.TypeGuard ? T : never export interface IframeCallback> { diff --git a/front/src/Api/iframe/room.ts b/front/src/Api/iframe/room.ts index 78fad58b..c70d0aad 100644 --- a/front/src/Api/iframe/room.ts +++ b/front/src/Api/iframe/room.ts @@ -4,7 +4,7 @@ import { isDataLayerEvent } from "../Events/DataLayerEvent"; import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent"; import { isGameStateEvent } from "../Events/GameStateEvent"; -import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; +import {IframeApiContribution, queryWorkadventure, sendToWorkadventure} from "./IframeApiContribution"; import { apiCallback } from "./registeredCallbacks"; import type { ITiledMap } from "../../Phaser/Map/ITiledMap"; @@ -16,7 +16,7 @@ const leaveStreams: Map> = new Map(); const stateResolvers = new Subject(); -let immutableData: GameStateEvent; +let immutableDataPromise: Promise | undefined = undefined; interface Room { id: string; @@ -39,14 +39,10 @@ interface TileDescriptor { } function getGameState(): Promise { - if (immutableData) { - return Promise.resolve(immutableData); - } else { - return new Promise((resolver, thrower) => { - stateResolvers.subscribe(resolver); - sendToWorkadventure({ type: "getState", data: null }); - }); + if (immutableDataPromise === undefined) { + immutableDataPromise = queryWorkadventure({ type: "getState", data: undefined }); } + return immutableDataPromise; } function getDataLayer(): Promise { @@ -72,13 +68,6 @@ export class WorkadventureRoomCommands extends IframeApiContribution { - stateResolvers.next(payloadData); - }, - }), apiCallback({ type: "dataLayer", typeChecker: isDataLayerEvent, diff --git a/front/src/Phaser/Components/TextUtils.ts b/front/src/Phaser/Components/TextUtils.ts index db9a97fb..972c50c7 100644 --- a/front/src/Phaser/Components/TextUtils.ts +++ b/front/src/Phaser/Components/TextUtils.ts @@ -44,7 +44,6 @@ export class TextUtils { options.align = object.text.halign; } - console.warn(options); const textElem = scene.add.text(object.x, object.y, object.text.text, options); textElem.setAngle(object.rotation); } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index d137c9e0..6cb5284d 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1044,18 +1044,16 @@ ${escapedMessage} }) ); - this.iframeSubscriptionList.push( - iframeListener.gameStateStream.subscribe(() => { - iframeListener.sendGameStateEvent({ - mapUrl: this.MapUrlFile, - startLayerName: this.startPositionCalculator.startLayerName, - uuid: localUserStore.getLocalUser()?.uuid, - nickname: localUserStore.getName(), - roomId: this.RoomId, - tags: this.connection ? this.connection.getAllTags() : [], - }); - }) - ); + iframeListener.registerAnswerer('getState', () => { + return Promise.resolve({ + mapUrl: this.MapUrlFile, + startLayerName: this.startPositionCalculator.startLayerName, + uuid: localUserStore.getLocalUser()?.uuid, + nickname: localUserStore.getName(), + roomId: this.RoomId, + tags: this.connection ? this.connection.getAllTags() : [], + }); + }); this.iframeSubscriptionList.push( iframeListener.setTilesStream.subscribe((eventTiles) => { for (const eventTile of eventTiles) { @@ -1149,6 +1147,7 @@ ${escapedMessage} this.emoteManager.destroy(); this.peerStoreUnsubscribe(); this.biggestAvailableAreaStoreUnsubscribe(); + iframeListener.unregisterAnswerer('getState'); mediaManager.hideGameOverlay(); diff --git a/front/src/iframe_api.ts b/front/src/iframe_api.ts index a1949106..1915020e 100644 --- a/front/src/iframe_api.ts +++ b/front/src/iframe_api.ts @@ -1,7 +1,7 @@ import { registeredCallbacks } from "./Api/iframe/registeredCallbacks"; import { IframeResponseEvent, - IframeResponseEventMap, + IframeResponseEventMap, isIframeAnswerEvent, isIframeErrorAnswerEvent, isIframeResponseEventWrapper, TypedMessageEvent, } from "./Api/Events/IframeEvent"; @@ -16,7 +16,7 @@ import player from "./Api/iframe/player"; import type { ButtonDescriptor } from "./Api/iframe/Ui/ButtonDescriptor"; import type { Popup } from "./Api/iframe/Ui/Popup"; import type { Sound } from "./Api/iframe/Sound/Sound"; -import { sendToWorkadventure } from "./Api/iframe/IframeApiContribution"; +import { answerPromises, sendToWorkadventure} from "./Api/iframe/IframeApiContribution"; const wa = { ui, @@ -164,16 +164,38 @@ declare global { window.WA = wa; window.addEventListener( - "message", - (message: TypedMessageEvent>) => { - if (message.source !== window.parent) { - return; // Skip message in this event listener - } - const payload = message.data; - console.debug(payload); + "message", (message: TypedMessageEvent>) => { + if (message.source !== window.parent) { + return; // Skip message in this event listener + } + const payload = message.data; - if (isIframeResponseEventWrapper(payload)) { - const payloadData = payload.data; + console.debug(payload); + + 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 | undefined; if (callback?.typeChecker(payloadData)) { From 280c59e6b5734b9821a62cb438f3e5e5d9660f45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 2 Jul 2021 17:26:28 +0200 Subject: [PATCH 2/2] Changing callback signature of registerAnswerer so that it can return a value and not necessarily a promise. --- docs/maps/api-room.md | 2 +- front/src/Api/IframeListener.ts | 6 +++--- front/src/Phaser/Game/GameScene.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/maps/api-room.md b/docs/maps/api-room.md index 17e3d48e..9d08ce1b 100644 --- a/docs/maps/api-room.md +++ b/docs/maps/api-room.md @@ -81,7 +81,7 @@ WA.room.getCurrentRoom(): Promise ``` Return a promise that resolves to a `Room` object with the following attributes : * **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 * **startLayer (string | null) :** Name of the layer where the current user started, only if different from `start` layer diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 334535a5..314d5d2e 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -33,7 +33,7 @@ import { isLoadPageEvent } from "./Events/LoadPageEvent"; import { handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent } from "./Events/ui/MenuItemRegisterEvent"; import { SetTilesEvent, isSetTilesEvent } from "./Events/SetTilesEvent"; -type AnswererCallback = (query: IframeQueryMap[T]['query']) => Promise; +type AnswererCallback = (query: IframeQueryMap[T]['query']) => IframeQueryMap[T]['answer']|Promise; /** * Listens to messages from iframes and turn those messages into easy to use observables. @@ -164,7 +164,7 @@ class IframeListener { return; } - answerer(query.data).then((value) => { + Promise.resolve(answerer(query.data)).then((value) => { iframe?.contentWindow?.postMessage({ id: queryId, type: query.type, @@ -411,7 +411,7 @@ class IframeListener { * @param key The "type" of the query we are answering * @param callback */ - public registerAnswerer(key: T, callback: (query: IframeQueryMap[T]['query']) => Promise ): void { + public registerAnswerer(key: T, callback: (query: IframeQueryMap[T]['query']) => IframeQueryMap[T]['answer']|Promise ): void { this.answerers[key] = callback; } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 6cb5284d..d767f0f4 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1045,14 +1045,14 @@ ${escapedMessage} ); iframeListener.registerAnswerer('getState', () => { - return Promise.resolve({ + return { mapUrl: this.MapUrlFile, startLayerName: this.startPositionCalculator.startLayerName, uuid: localUserStore.getLocalUser()?.uuid, nickname: localUserStore.getName(), roomId: this.RoomId, tags: this.connection ? this.connection.getAllTags() : [], - }); + }; }); this.iframeSubscriptionList.push( iframeListener.setTilesStream.subscribe((eventTiles) => {