Fix widgets re-appearing after being deleted
Widgets would sometimes briefly re-appear after having been deleted. This was because of the following race: * User presses delete, send POST req, we set `deleting`. Widget hides. * POST request completes, we unset `deleting` so widget unhides. * State event comes down sync so widget hides again. This fixes this by introducing `waitForRoomWidget` and using it to wait until the state event comes down the sync until clearing the `deleting` flag. Since we now have `waitForRoomWidget`, this also uses it when adding a widget so the 'widget saved' appears at the same time the widget does.
This commit is contained in:
parent
1cb794753e
commit
94125fb566
3 changed files with 64 additions and 9 deletions
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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});
|
||||
|
|
Loading…
Reference in a new issue