From 71d06b4d59bc084be3a5d3c0aa88346a69ebf0b0 Mon Sep 17 00:00:00 2001 From: "mikhail.aheichyk" Date: Tue, 20 Dec 2022 11:01:14 +0300 Subject: [PATCH] Widget receives updated state events if user is re-invited into the room. --- cypress/e2e/widgets/events.spec.ts | 205 ++++++++++++++++++ .../templates/default/homeserver.yaml | 4 + src/stores/widgets/StopGapWidget.ts | 8 +- 3 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 cypress/e2e/widgets/events.spec.ts diff --git a/cypress/e2e/widgets/events.spec.ts b/cypress/e2e/widgets/events.spec.ts new file mode 100644 index 0000000000..aeef1d5c76 --- /dev/null +++ b/cypress/e2e/widgets/events.spec.ts @@ -0,0 +1,205 @@ +/* +Copyright 2022 Mikhail Aheichyk +Copyright 2022 Nordeck IT + Consulting GmbH. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/// + +import { IWidget } from "matrix-widget-api/src/interfaces/IWidget"; + +import type {MatrixClient, MatrixEvent, Room} from "matrix-js-sdk/src/matrix"; +import { SynapseInstance } from "../../plugins/synapsedocker"; +import { UserCredentials } from "../../support/login"; + +const DEMO_WIDGET_ID = "demo-widget-id"; +const DEMO_WIDGET_NAME = "Demo Widget"; +const DEMO_WIDGET_TYPE = "demo"; +const ROOM_NAME = "Demo"; + +const DEMO_WIDGET_HTML = ` + + + Demo Widget + + + + + + +`; + +function waitForRoom(win: Cypress.AUTWindow, roomId: string, predicate: (room: Room) => boolean): Promise { + const matrixClient = win.mxMatrixClientPeg.get(); + + return new Promise((resolve, reject) => { + const room = matrixClient.getRoom(roomId); + + if (predicate(room)) { + resolve(); + return; + } + + function onEvent(ev: MatrixEvent) { + if (ev.getRoomId() !== roomId) return; + + if (predicate(room)) { + matrixClient.removeListener(win.matrixcs.ClientEvent.Event, onEvent); + resolve(); + } + } + + matrixClient.on(win.matrixcs.ClientEvent.Event, onEvent); + }); +} + +describe("Widget Events", () => { + let synapse: SynapseInstance; + let user: UserCredentials; + let bot: MatrixClient; + let demoWidgetUrl: string; + + beforeEach(() => { + cy.startSynapse("default").then(data => { + synapse = data; + + cy.initTestUser(synapse, "Mike").then(_user => { + user = _user; + }); + cy.getBot(synapse, { displayName: "Bot", autoAcceptInvites: true }).then(_bot => { + bot = _bot; + }); + }); + cy.serveHtmlFile(DEMO_WIDGET_HTML).then(url => { + demoWidgetUrl = url; + }); + }); + + afterEach(() => { + cy.stopSynapse(synapse); + cy.stopWebServers(); + }); + + it('should be updated if user is re-invited into the room with updated state event', () => { + cy.createRoom({ + name: ROOM_NAME, + invite: [bot.getUserId()], + }).then(roomId => { + // setup widget via state event + cy.getClient().then(async matrixClient => { + const content: IWidget = { + id: DEMO_WIDGET_ID, + creatorUserId: 'somebody', + type: DEMO_WIDGET_TYPE, + name: DEMO_WIDGET_NAME, + url: demoWidgetUrl, + }; + await matrixClient.sendStateEvent(roomId, 'im.vector.modular.widgets', content, DEMO_WIDGET_ID); + }).as('widgetEventSent'); + + // set initial layout + cy.getClient().then(async matrixClient => { + const content = { + widgets: { + [DEMO_WIDGET_ID]: { + container: 'top', index: 1, width: 100, height: 0, + }, + }, + }; + await matrixClient.sendStateEvent(roomId, 'io.element.widgets.layout', content, ""); + }).as('layoutEventSent'); + + // open the room + cy.viewRoomByName(ROOM_NAME); + + // approve capabilities + cy.contains('.mx_WidgetCapabilitiesPromptDialog button', 'Approve').click(); + + cy.all([ + cy.get("@widgetEventSent"), + cy.get("@layoutEventSent"), + ]).then(async () => { + // bot creates a new room with 'net.metadata_invite_shared' state event + const { room_id: roomNew } = await bot.createRoom({ + name: "New room", + initial_state: [ + { + type: 'net.metadata_invite_shared', + state_key: '', + content: { + value: "initial", + }, + }, + ], + }); + + await bot.invite(roomNew, user.userId); + + // widget should receive 'net.metadata_invite_shared' event after invite + cy.window().then(async win => { + await waitForRoom(win, roomId, (room) => { + const events = room.getLiveTimeline().getEvents(); + return events.some(e => e.getType() === 'net.widget_echo' + && e.getContent().type === 'net.metadata_invite_shared' + && e.getContent().content.value === 'initial'); + }); + }); + + await bot.sendStateEvent(roomNew, 'net.metadata_invite_shared', { + value: "new_value", + }, ''); + + await bot.invite(roomNew, user.userId, 'something changed in the room'); + + // widget should receive updated 'net.metadata_invite_shared' event after re-invite + cy.window().then(async win => { + await waitForRoom(win, roomId, (room) => { + const events = room.getLiveTimeline().getEvents(); + return events.some(e => e.getType() === 'net.widget_echo' + && e.getContent().type === 'net.metadata_invite_shared' + && e.getContent().content.value === 'new_value'); + }); + }); + }); + }); + }); +}); diff --git a/cypress/plugins/synapsedocker/templates/default/homeserver.yaml b/cypress/plugins/synapsedocker/templates/default/homeserver.yaml index aaad3420b9..e282e790e9 100644 --- a/cypress/plugins/synapsedocker/templates/default/homeserver.yaml +++ b/cypress/plugins/synapsedocker/templates/default/homeserver.yaml @@ -74,3 +74,7 @@ suppress_key_server_warning: true ui_auth: session_timeout: "300s" + +room_prejoin_state: + additional_event_types: + - net.metadata_invite_shared diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index 9230dda12e..f7586e86ec 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -520,7 +520,13 @@ export class StopGapWidget extends EventEmitter { } } - this.readUpToMap[ev.getRoomId()] = ev.getId(); + // Skip marker assignment if membership is 'invite', otherwise 'm.room.member' from + // invitation room will assign it and new state events will be not forwarded to the widget + // because of empty timeline for invitation room and assigned marker. + const room = this.client.getRoom(ev.getRoomId()); + if (room && room.getMyMembership() !== 'invite') { + this.readUpToMap[ev.getRoomId()] = ev.getId(); + } const raw = ev.getEffectiveEvent(); this.messaging.feedEvent(raw as IRoomEvent, this.eventListenerRoomId).catch((e) => {