Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 7af4891

Browse files
authored
Merge pull request #9802 from nordeck/widget_state_no_update_invitation_room
State event updates are not forwarded to the widget from invitation room
2 parents a0de225 + 3c35774 commit 7af4891

File tree

2 files changed

+232
-1
lines changed

2 files changed

+232
-1
lines changed

cypress/e2e/widgets/events.spec.ts

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
/*
2+
Copyright 2022 Mikhail Aheichyk
3+
Copyright 2022 Nordeck IT + Consulting GmbH.
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
/// <reference types="cypress" />
19+
20+
import { IWidget } from "matrix-widget-api/src/interfaces/IWidget";
21+
22+
import type { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
23+
import { HomeserverInstance } from "../../plugins/utils/homeserver";
24+
import { UserCredentials } from "../../support/login";
25+
26+
const DEMO_WIDGET_ID = "demo-widget-id";
27+
const DEMO_WIDGET_NAME = "Demo Widget";
28+
const DEMO_WIDGET_TYPE = "demo";
29+
const ROOM_NAME = "Demo";
30+
31+
const DEMO_WIDGET_HTML = `
32+
<html lang="en">
33+
<head>
34+
<title>Demo Widget</title>
35+
<script>
36+
let sendEventCount = 0
37+
window.onmessage = ev => {
38+
if (ev.data.action === 'capabilities') {
39+
window.parent.postMessage(Object.assign({
40+
response: {
41+
capabilities: [
42+
"org.matrix.msc2762.timeline:*",
43+
"org.matrix.msc2762.receive.state_event:m.room.topic",
44+
"org.matrix.msc2762.send.event:net.widget_echo"
45+
]
46+
},
47+
}, ev.data), '*');
48+
} else if (ev.data.action === 'send_event' && !ev.data.response) {
49+
// wraps received event into 'net.widget_echo' and sends back
50+
sendEventCount += 1
51+
window.parent.postMessage({
52+
api: "fromWidget",
53+
widgetId: ev.data.widgetId,
54+
requestId: 'widget-' + sendEventCount,
55+
action: "send_event",
56+
data: {
57+
type: 'net.widget_echo',
58+
content: ev.data.data // sets matrix event to the content returned
59+
},
60+
}, '*')
61+
}
62+
};
63+
</script>
64+
</head>
65+
<body>
66+
<button id="demo">Demo</button>
67+
</body>
68+
</html>
69+
`;
70+
71+
function waitForRoom(win: Cypress.AUTWindow, roomId: string, predicate: (room: Room) => boolean): Promise<void> {
72+
const matrixClient = win.mxMatrixClientPeg.get();
73+
74+
return new Promise((resolve, reject) => {
75+
const room = matrixClient.getRoom(roomId);
76+
77+
if (predicate(room)) {
78+
resolve();
79+
return;
80+
}
81+
82+
function onEvent(ev: MatrixEvent) {
83+
if (ev.getRoomId() !== roomId) return;
84+
85+
if (predicate(room)) {
86+
matrixClient.removeListener(win.matrixcs.ClientEvent.Event, onEvent);
87+
resolve();
88+
}
89+
}
90+
91+
matrixClient.on(win.matrixcs.ClientEvent.Event, onEvent);
92+
});
93+
}
94+
95+
describe("Widget Events", () => {
96+
let homeserver: HomeserverInstance;
97+
let user: UserCredentials;
98+
let bot: MatrixClient;
99+
let demoWidgetUrl: string;
100+
101+
beforeEach(() => {
102+
cy.startHomeserver("default").then((data) => {
103+
homeserver = data;
104+
105+
cy.initTestUser(homeserver, "Mike").then((_user) => {
106+
user = _user;
107+
});
108+
cy.getBot(homeserver, { displayName: "Bot", autoAcceptInvites: true }).then((_bot) => {
109+
bot = _bot;
110+
});
111+
});
112+
cy.serveHtmlFile(DEMO_WIDGET_HTML).then((url) => {
113+
demoWidgetUrl = url;
114+
});
115+
});
116+
117+
afterEach(() => {
118+
cy.stopHomeserver(homeserver);
119+
cy.stopWebServers();
120+
});
121+
122+
it("should be updated if user is re-invited into the room with updated state event", () => {
123+
cy.createRoom({
124+
name: ROOM_NAME,
125+
invite: [bot.getUserId()],
126+
}).then((roomId) => {
127+
// setup widget via state event
128+
cy.getClient()
129+
.then(async (matrixClient) => {
130+
const content: IWidget = {
131+
id: DEMO_WIDGET_ID,
132+
creatorUserId: "somebody",
133+
type: DEMO_WIDGET_TYPE,
134+
name: DEMO_WIDGET_NAME,
135+
url: demoWidgetUrl,
136+
};
137+
await matrixClient.sendStateEvent(roomId, "im.vector.modular.widgets", content, DEMO_WIDGET_ID);
138+
})
139+
.as("widgetEventSent");
140+
141+
// set initial layout
142+
cy.getClient()
143+
.then(async (matrixClient) => {
144+
const content = {
145+
widgets: {
146+
[DEMO_WIDGET_ID]: {
147+
container: "top",
148+
index: 1,
149+
width: 100,
150+
height: 0,
151+
},
152+
},
153+
};
154+
await matrixClient.sendStateEvent(roomId, "io.element.widgets.layout", content, "");
155+
})
156+
.as("layoutEventSent");
157+
158+
// open the room
159+
cy.viewRoomByName(ROOM_NAME);
160+
161+
// approve capabilities
162+
cy.contains(".mx_WidgetCapabilitiesPromptDialog button", "Approve").click();
163+
164+
cy.all([cy.get<string>("@widgetEventSent"), cy.get<string>("@layoutEventSent")]).then(async () => {
165+
// bot creates a new room with 'm.room.topic'
166+
const { room_id: roomNew } = await bot.createRoom({
167+
name: "New room",
168+
initial_state: [
169+
{
170+
type: "m.room.topic",
171+
state_key: "",
172+
content: {
173+
topic: "topic initial",
174+
},
175+
},
176+
],
177+
});
178+
179+
await bot.invite(roomNew, user.userId);
180+
181+
// widget should receive 'm.room.topic' event after invite
182+
cy.window().then(async (win) => {
183+
await waitForRoom(win, roomId, (room) => {
184+
const events = room.getLiveTimeline().getEvents();
185+
return events.some(
186+
(e) =>
187+
e.getType() === "net.widget_echo" &&
188+
e.getContent().type === "m.room.topic" &&
189+
e.getContent().content.topic === "topic initial",
190+
);
191+
});
192+
});
193+
194+
// update the topic
195+
await bot.sendStateEvent(
196+
roomNew,
197+
"m.room.topic",
198+
{
199+
topic: "topic updated",
200+
},
201+
"",
202+
);
203+
204+
await bot.invite(roomNew, user.userId, "something changed in the room");
205+
206+
// widget should receive updated 'm.room.topic' event after re-invite
207+
cy.window().then(async (win) => {
208+
await waitForRoom(win, roomId, (room) => {
209+
const events = room.getLiveTimeline().getEvents();
210+
return events.some(
211+
(e) =>
212+
e.getType() === "net.widget_echo" &&
213+
e.getContent().type === "m.room.topic" &&
214+
e.getContent().content.topic === "topic updated",
215+
);
216+
});
217+
});
218+
});
219+
});
220+
});
221+
});

src/stores/widgets/StopGapWidget.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,17 @@ export class StopGapWidget extends EventEmitter {
519519
}
520520
}
521521

522-
this.readUpToMap[ev.getRoomId()] = ev.getId();
522+
// Skip marker assignment if membership is 'invite', otherwise 'm.room.member' from
523+
// invitation room will assign it and new state events will be not forwarded to the widget
524+
// because of empty timeline for invitation room and assigned marker.
525+
const evRoomId = ev.getRoomId();
526+
const evId = ev.getId();
527+
if (evRoomId && evId) {
528+
const room = this.client.getRoom(evRoomId);
529+
if (room && room.getMyMembership() === "join") {
530+
this.readUpToMap[evRoomId] = evId;
531+
}
532+
}
523533

524534
const raw = ev.getEffectiveEvent();
525535
this.messaging.feedEvent(raw as IRoomEvent, this.eventListenerRoomId).catch((e) => {

0 commit comments

Comments
 (0)