Merge pull request #1958 from matrix-org/dbkr/widget_waiting

Fix widgets re-appearing after being deleted
pull/21833/head
Luke Barnard 2018-06-14 17:31:23 +01:00 committed by GitHub
commit 5077e9e89b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 168 additions and 138 deletions

View File

@ -1,6 +1,7 @@
/* /*
Copyright 2016 OpenMarket Ltd Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -231,11 +232,13 @@ Example:
} }
*/ */
const SdkConfig = require('./SdkConfig'); import SdkConfig from './SdkConfig';
const MatrixClientPeg = require("./MatrixClientPeg"); import MatrixClientPeg from './MatrixClientPeg';
const MatrixEvent = require("matrix-js-sdk").MatrixEvent; import { MatrixEvent } from 'matrix-js-sdk';
const dis = require("./dispatcher"); import dis from './dispatcher';
const Widgets = require('./utils/widgets'); import Widgets from './utils/widgets';
import WidgetUtils from './WidgetUtils';
import RoomViewStore from './stores/RoomViewStore';
import { _t } from './languageHandler'; import { _t } from './languageHandler';
function sendResponse(event, res) { function sendResponse(event, res) {
@ -286,51 +289,6 @@ function inviteUser(event, roomId, userId) {
}); });
} }
/**
* Returns a promise that resolves when a widget with the given
* ID has been added as a user widget (ie. the accountData event
* arrives) or rejects after a timeout
*
* @param {string} widgetId The ID of the widget to wait for
* @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
*/
function 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 (add) {
return ev.getContent()[widgetId] !== undefined;
} else {
return ev.getContent()[widgetId] === undefined;
}
}
if (eventInIntendedState(currentAccountDataEvent)) {
resolve();
return;
}
function onAccountData(ev) {
if (eventInIntendedState(currentAccountDataEvent)) {
MatrixClientPeg.get().removeListener('accountData', onAccountData);
clearTimeout(timerId);
resolve();
}
}
const timerId = setTimeout(() => {
MatrixClientPeg.get().removeListener('accountData', onAccountData);
reject(new Error("Timed out waiting for widget ID " + widgetId + " to appear"));
}, 10000);
MatrixClientPeg.get().on('accountData', onAccountData);
});
}
function setWidget(event, roomId) { function setWidget(event, roomId) {
const widgetId = event.data.widget_id; const widgetId = event.data.widget_id;
const widgetType = event.data.type; const widgetType = event.data.type;
@ -405,7 +363,7 @@ function setWidget(event, roomId) {
// wait for this, the action will complete but if the user is fast enough, // wait for this, the action will complete but if the user is fast enough,
// the widget still won't actually be there. // the widget still won't actually be there.
client.setAccountData('m.widgets', userWidgets).then(() => { client.setAccountData('m.widgets', userWidgets).then(() => {
return waitForUserWidget(widgetId, widgetUrl !== null); return WidgetUtils.waitForUserWidget(widgetId, widgetUrl !== null);
}).then(() => { }).then(() => {
sendResponse(event, { sendResponse(event, {
success: true, success: true,
@ -425,9 +383,9 @@ function setWidget(event, roomId) {
} }
// TODO - Room widgets need to be moved to 'm.widget' state events // TODO - Room widgets need to be moved to 'm.widget' state events
// https://docs.google.com/document/d/1uPF7XWY_dXTKVKV7jZQ2KmsI19wn9-kFRgQ1tFQP7wQ/edit?usp=sharing // https://docs.google.com/document/d/1uPF7XWY_dXTKVKV7jZQ2KmsI19wn9-kFRgQ1tFQP7wQ/edit?usp=sharing
client.sendStateEvent(roomId, "im.vector.modular.widgets", content, widgetId).done(() => { client.sendStateEvent(roomId, "im.vector.modular.widgets", content, widgetId).then(() => {
// XXX: We should probably wait for the echo of the state event to come back from the server, return WidgetUtils.waitForRoomWidget(widgetId, roomId, widgetUrl !== null);
// as we do with user widgets. }).then(() => {
sendResponse(event, { sendResponse(event, {
success: true, success: true,
}); });
@ -637,19 +595,6 @@ function returnStateEvent(event, roomId, eventType, stateKey) {
sendResponse(event, stateEvent.getContent()); sendResponse(event, stateEvent.getContent());
} }
let currentRoomId = null;
let currentRoomAlias = null;
// Listen for when a room is viewed
dis.register(onAction);
function onAction(payload) {
if (payload.action !== "view_room") {
return;
}
currentRoomId = payload.room_id;
currentRoomAlias = payload.room_alias;
}
const onMessage = function(event) { const onMessage = function(event) {
if (!event.origin) { // stupid chrome if (!event.origin) { // stupid chrome
event.origin = event.originalEvent.origin; event.origin = event.originalEvent.origin;
@ -700,80 +645,63 @@ const onMessage = function(event) {
return; return;
} }
} }
let promise = Promise.resolve(currentRoomId);
if (!currentRoomId) { if (roomId !== RoomViewStore.getRoomId()) {
if (!currentRoomAlias) { sendError(event, _t('Room %(roomId)s not visible', {roomId: roomId}));
sendError(event, _t('Must be viewing a room')); return;
return;
}
// no room ID but there is an alias, look it up.
console.log("Looking up alias " + currentRoomAlias);
promise = MatrixClientPeg.get().getRoomIdForAlias(currentRoomAlias).then((res) => {
return res.room_id;
});
} }
promise.then((viewingRoomId) => { // Get and set room-based widgets
if (roomId !== viewingRoomId) { if (event.data.action === "get_widgets") {
sendError(event, _t('Room %(roomId)s not visible', {roomId: roomId})); getWidgets(event, roomId);
return; return;
} } else if (event.data.action === "set_widget") {
setWidget(event, roomId);
return;
}
// Get and set room-based widgets // These APIs don't require userId
if (event.data.action === "get_widgets") { if (event.data.action === "join_rules_state") {
getWidgets(event, roomId); getJoinRules(event, roomId);
return; return;
} else if (event.data.action === "set_widget") { } else if (event.data.action === "set_plumbing_state") {
setWidget(event, roomId); setPlumbingState(event, roomId, event.data.status);
return; return;
} } else if (event.data.action === "get_membership_count") {
getMembershipCount(event, roomId);
return;
} else if (event.data.action === "get_room_enc_state") {
getRoomEncState(event, roomId);
return;
} else if (event.data.action === "can_send_event") {
canSendEvent(event, roomId);
return;
}
// These APIs don't require userId if (!userId) {
if (event.data.action === "join_rules_state") { sendError(event, _t('Missing user_id in request'));
getJoinRules(event, roomId); return;
return; }
} else if (event.data.action === "set_plumbing_state") { switch (event.data.action) {
setPlumbingState(event, roomId, event.data.status); case "membership_state":
return; getMembershipState(event, roomId, userId);
} else if (event.data.action === "get_membership_count") { break;
getMembershipCount(event, roomId); case "invite":
return; inviteUser(event, roomId, userId);
} else if (event.data.action === "get_room_enc_state") { break;
getRoomEncState(event, roomId); case "bot_options":
return; botOptions(event, roomId, userId);
} else if (event.data.action === "can_send_event") { break;
canSendEvent(event, roomId); case "set_bot_options":
return; setBotOptions(event, roomId, userId);
} break;
case "set_bot_power":
if (!userId) { setBotPower(event, roomId, userId, event.data.level);
sendError(event, _t('Missing user_id in request')); break;
return; default:
} console.warn("Unhandled postMessage event with action '" + event.data.action +"'");
switch (event.data.action) { break;
case "membership_state": }
getMembershipState(event, roomId, userId);
break;
case "invite":
inviteUser(event, roomId, userId);
break;
case "bot_options":
botOptions(event, roomId, userId);
break;
case "set_bot_options":
setBotOptions(event, roomId, userId);
break;
case "set_bot_power":
setBotPower(event, roomId, userId, event.data.level);
break;
default:
console.warn("Unhandled postMessage event with action '" + event.data.action +"'");
break;
}
}, (err) => {
console.error(err);
sendError(event, _t('Failed to lookup current room') + '.');
});
}; };
let listenerCount = 0; let listenerCount = 0;

View File

@ -1,5 +1,6 @@
/* /*
Copyright 2017 Vector Creations Ltd Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -90,4 +91,103 @@ export default class WidgetUtils {
} }
return false; return false;
} }
/**
* Returns a promise that resolves when a widget with the given
* ID has been added as a user widget (ie. the accountData event
* arrives) or rejects after a timeout
*
* @param {string} widgetId The ID of the widget to wait for
* @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 in the
* requested state according to the `add` param
*/
static waitForUserWidget(widgetId, add) {
return new Promise((resolve, reject) => {
// 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 || !ev.getContent()) return false;
if (add) {
return ev.getContent()[widgetId] !== undefined;
} else {
return ev.getContent()[widgetId] === undefined;
}
}
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);
resolve();
}
}
const timerId = setTimeout(() => {
MatrixClientPeg.get().removeListener('accountData', onAccountData);
reject(new Error("Timed out waiting for widget ID " + widgetId + " to appear"));
}, 10000);
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 in the
* requested state according to the `add` param
*/
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);
});
}
} }

View File

@ -324,7 +324,9 @@ export default class AppTile extends React.Component {
'im.vector.modular.widgets', 'im.vector.modular.widgets',
{}, // empty content {}, // empty content
this.props.id, 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); console.error('Failed to delete widget', e);
}).finally(() => { }).finally(() => {
this.setState({deleting: false}); this.setState({deleting: false});