diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js index dd3975dfe5..f80162e635 100644 --- a/src/ScalarMessaging.js +++ b/src/ScalarMessaging.js @@ -237,6 +237,7 @@ import MatrixClientPeg from './MatrixClientPeg'; import { MatrixEvent } from 'matrix-js-sdk'; import dis from './dispatcher'; import Widgets from './utils/widgets'; +import WidgetUtils from './WidgetUtils'; import RoomViewStore from './stores/RoomViewStore'; import { _t } from './languageHandler'; @@ -362,7 +363,7 @@ function setWidget(event, roomId) { // wait for this, the action will complete but if the user is fast enough, // the widget still won't actually be there. client.setAccountData('m.widgets', userWidgets).then(() => { - return waitForUserWidget(widgetId, widgetUrl !== null); + return WidgetUtils.waitForUserWidget(widgetId, widgetUrl !== null); }).then(() => { sendResponse(event, { success: true, @@ -382,9 +383,9 @@ function setWidget(event, roomId) { } // TODO - Room widgets need to be moved to 'm.widget' state events // https://docs.google.com/document/d/1uPF7XWY_dXTKVKV7jZQ2KmsI19wn9-kFRgQ1tFQP7wQ/edit?usp=sharing - client.sendStateEvent(roomId, "im.vector.modular.widgets", content, widgetId).done(() => { - // XXX: We should probably wait for the echo of the state event to come back from the server, - // as we do with user widgets. + client.sendStateEvent(roomId, "im.vector.modular.widgets", content, widgetId).then(() => { + return WidgetUtils.waitForRoomWidget(widgetId, roomId, widgetUrl !== null); + }).then(() => { sendResponse(event, { success: true, }); diff --git a/src/WidgetUtils.js b/src/WidgetUtils.js index c6816d28b6..14fe3f59bd 100644 --- a/src/WidgetUtils.js +++ b/src/WidgetUtils.js @@ -104,12 +104,10 @@ export default class WidgetUtils { */ static waitForUserWidget(widgetId, add) { return new Promise((resolve, reject) => { - const currentAccountDataEvent = MatrixClientPeg.get().getAccountData('m.widgets'); - // Tests an account data event, returning true if it's in the state // we're waiting for it to be in function eventInIntendedState(ev) { - if (!ev || !currentAccountDataEvent.getContent()) return false; + if (!ev || !ev.getContent()) return false; if (add) { return ev.getContent()[widgetId] !== undefined; } else { @@ -117,12 +115,14 @@ export default class WidgetUtils { } } - if (eventInIntendedState(currentAccountDataEvent)) { + const startingAccountDataEvent = MatrixClientPeg.get().getAccountData('m.widgets'); + if (eventInIntendedState(startingAccountDataEvent)) { resolve(); return; } function onAccountData(ev) { + const currentAccountDataEvent = MatrixClientPeg.get().getAccountData('m.widgets'); if (eventInIntendedState(currentAccountDataEvent)) { MatrixClientPeg.get().removeListener('accountData', onAccountData); clearTimeout(timerId); @@ -136,4 +136,56 @@ export default class WidgetUtils { MatrixClientPeg.get().on('accountData', onAccountData); }); } + + /** + * Returns a promise that resolves when a widget with the given + * ID has been added as a room widget in the given room (ie. the + * room state event arrives) or rejects after a timeout + * + * @param {string} widgetId The ID of the widget to wait for + * @param {string} roomId The ID of the room to wait for the widget in + * @param {boolean} add True to wait for the widget to be added, + * false to wait for it to be deleted. + * @returns {Promise} that resolves when the widget is available + */ + static waitForRoomWidget(widgetId, roomId, add) { + return new Promise((resolve, reject) => { + // Tests a list of state events, returning true if it's in the state + // we're waiting for it to be in + function eventsInIntendedState(evList) { + const widgetPresent = evList.some((ev) => { + return ev.getContent() && ev.getContent()['id'] === widgetId; + }); + if (add) { + return widgetPresent; + } else { + return !widgetPresent; + } + } + + const room = MatrixClientPeg.get().getRoom(roomId); + const startingWidgetEvents = room.currentState.getStateEvents('im.vector.modular.widgets'); + if (eventsInIntendedState(startingWidgetEvents)) { + resolve(); + return; + } + + function onRoomStateEvents(ev) { + if (ev.getRoomId() !== roomId) return; + + const currentWidgetEvents = room.currentState.getStateEvents('im.vector.modular.widgets'); + + if (eventsInIntendedState(currentWidgetEvents)) { + MatrixClientPeg.get().removeListener('RoomState.events', onRoomStateEvents); + clearTimeout(timerId); + resolve(); + } + } + const timerId = setTimeout(() => { + MatrixClientPeg.get().removeListener('RoomState.events', onRoomStateEvents); + reject(new Error("Timed out waiting for widget ID " + widgetId + " to appear")); + }, 10000); + MatrixClientPeg.get().on('RoomState.events', onRoomStateEvents); + }); + } } diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 429b5941b9..70b5bd651e 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -324,7 +324,9 @@ export default class AppTile extends React.Component { 'im.vector.modular.widgets', {}, // empty content this.props.id, - ).catch((e) => { + ).then(() => { + return WidgetUtils.waitForRoomWidget(this.props.id, this.props.room.roomId, false); + }).catch((e) => { console.error('Failed to delete widget', e); }).finally(() => { this.setState({deleting: false});