Skip to content

Commit de69445

Browse files
authored
Target widget actions at a specific room (#2670)
Otherwise, the RoomWidgetClient class can end up accidentally sending and receiving events from rooms it didn't intend to, if it's an always-on-screen widget.
1 parent 6fc9827 commit de69445

File tree

2 files changed

+47
-34
lines changed

2 files changed

+47
-34
lines changed

spec/unit/embedded.spec.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class MockWidgetApi extends EventEmitter {
4242
public start = jest.fn();
4343
public requestCapability = jest.fn();
4444
public requestCapabilities = jest.fn();
45+
public requestCapabilityForRoomTimeline = jest.fn();
4546
public requestCapabilityToSendState = jest.fn();
4647
public requestCapabilityToReceiveState = jest.fn();
4748
public requestCapabilityToSendToDevice = jest.fn();
@@ -86,20 +87,17 @@ describe("RoomWidgetClient", () => {
8687

8788
it("sends", async () => {
8889
await makeClient({ sendState: [{ eventType: "org.example.foo", stateKey: "bar" }] });
90+
expect(widgetApi.requestCapabilityForRoomTimeline).toHaveBeenCalledWith("!1:example.org");
8991
expect(widgetApi.requestCapabilityToSendState).toHaveBeenCalledWith("org.example.foo", "bar");
9092
await client.sendStateEvent("!1:example.org", "org.example.foo", { hello: "world" }, "bar");
91-
expect(widgetApi.sendStateEvent).toHaveBeenCalledWith("org.example.foo", "bar", { hello: "world" });
92-
});
93-
94-
it("refuses to send to other rooms", async () => {
95-
await makeClient({ sendState: [{ eventType: "org.example.foo", stateKey: "bar" }] });
96-
expect(widgetApi.requestCapabilityToSendState).toHaveBeenCalledWith("org.example.foo", "bar");
97-
await expect(client.sendStateEvent("!2:example.org", "org.example.foo", { hello: "world" }, "bar"))
98-
.rejects.toBeDefined();
93+
expect(widgetApi.sendStateEvent).toHaveBeenCalledWith(
94+
"org.example.foo", "bar", { hello: "world" }, "!1:example.org",
95+
);
9996
});
10097

10198
it("receives", async () => {
10299
await makeClient({ receiveState: [{ eventType: "org.example.foo", stateKey: "bar" }] });
100+
expect(widgetApi.requestCapabilityForRoomTimeline).toHaveBeenCalledWith("!1:example.org");
103101
expect(widgetApi.requestCapabilityToReceiveState).toHaveBeenCalledWith("org.example.foo", "bar");
104102

105103
const emittedEvent = new Promise<MatrixEvent>(resolve => client.once(ClientEvent.Event, resolve));
@@ -114,7 +112,8 @@ describe("RoomWidgetClient", () => {
114112
expect(await emittedSync).toEqual(SyncState.Syncing);
115113
// It should've also inserted the event into the room object
116114
const room = client.getRoom("!1:example.org");
117-
expect(room.currentState.getStateEvents("org.example.foo", "bar").getEffectiveEvent()).toEqual(event);
115+
expect(room).not.toBeNull();
116+
expect(room!.currentState.getStateEvents("org.example.foo", "bar").getEffectiveEvent()).toEqual(event);
118117
});
119118

120119
it("backfills", async () => {
@@ -125,10 +124,12 @@ describe("RoomWidgetClient", () => {
125124
);
126125

127126
await makeClient({ receiveState: [{ eventType: "org.example.foo", stateKey: "bar" }] });
127+
expect(widgetApi.requestCapabilityForRoomTimeline).toHaveBeenCalledWith("!1:example.org");
128128
expect(widgetApi.requestCapabilityToReceiveState).toHaveBeenCalledWith("org.example.foo", "bar");
129129

130130
const room = client.getRoom("!1:example.org");
131-
expect(room.currentState.getStateEvents("org.example.foo", "bar").getEffectiveEvent()).toEqual(event);
131+
expect(room).not.toBeNull();
132+
expect(room!.currentState.getStateEvents("org.example.foo", "bar").getEffectiveEvent()).toEqual(event);
132133
});
133134
});
134135

@@ -257,7 +258,7 @@ describe("RoomWidgetClient", () => {
257258
const emittedServer = new Promise<IClientTurnServer[]>(resolve =>
258259
client.once(ClientEvent.TurnServers, resolve),
259260
);
260-
emitServer2();
261+
emitServer2!();
261262
expect(await emittedServer).toEqual([clientServer2]);
262263
expect(client.getTurnServers()).toEqual([clientServer2]);
263264
});

src/embedded.ts

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
ISendToDeviceToWidgetActionRequest,
2525
} from "matrix-widget-api";
2626

27+
import type { IEvent, IContent } from "./models/event";
2728
import { ISendEventResponse } from "./@types/requests";
2829
import { EventType } from "./@types/event";
2930
import { logger } from "./logger";
@@ -69,27 +70,30 @@ export class RoomWidgetClient extends MatrixClient {
6970
super(opts);
7071

7172
// Request capabilities for the functionality this client needs to support
72-
this.capabilities.sendState?.forEach(({ eventType, stateKey }) =>
73-
this.widgetApi.requestCapabilityToSendState(eventType, stateKey),
73+
if (capabilities.sendState?.length || capabilities.receiveState?.length) {
74+
widgetApi.requestCapabilityForRoomTimeline(roomId);
75+
}
76+
capabilities.sendState?.forEach(({ eventType, stateKey }) =>
77+
widgetApi.requestCapabilityToSendState(eventType, stateKey),
7478
);
75-
this.capabilities.receiveState?.forEach(({ eventType, stateKey }) =>
76-
this.widgetApi.requestCapabilityToReceiveState(eventType, stateKey),
79+
capabilities.receiveState?.forEach(({ eventType, stateKey }) =>
80+
widgetApi.requestCapabilityToReceiveState(eventType, stateKey),
7781
);
78-
this.capabilities.sendToDevice?.forEach(eventType =>
79-
this.widgetApi.requestCapabilityToSendToDevice(eventType),
82+
capabilities.sendToDevice?.forEach(eventType =>
83+
widgetApi.requestCapabilityToSendToDevice(eventType),
8084
);
81-
this.capabilities.receiveToDevice?.forEach(eventType =>
82-
this.widgetApi.requestCapabilityToReceiveToDevice(eventType),
85+
capabilities.receiveToDevice?.forEach(eventType =>
86+
widgetApi.requestCapabilityToReceiveToDevice(eventType),
8387
);
84-
if (this.capabilities.turnServers) {
85-
this.widgetApi.requestCapability(MatrixCapabilities.MSC3846TurnServers);
88+
if (capabilities.turnServers) {
89+
widgetApi.requestCapability(MatrixCapabilities.MSC3846TurnServers);
8690
}
8791

88-
this.widgetApi.on(`action:${WidgetApiToWidgetAction.SendEvent}`, this.onEvent);
89-
this.widgetApi.on(`action:${WidgetApiToWidgetAction.SendToDevice}`, this.onToDevice);
92+
widgetApi.on(`action:${WidgetApiToWidgetAction.SendEvent}`, this.onEvent);
93+
widgetApi.on(`action:${WidgetApiToWidgetAction.SendToDevice}`, this.onToDevice);
9094

9195
// Open communication with the host
92-
this.widgetApi.start();
96+
widgetApi.start();
9397
}
9498

9599
public async startClient(opts: IStartClientOpts = {}): Promise<void> {
@@ -121,8 +125,8 @@ export class RoomWidgetClient extends MatrixClient {
121125
// so it doesn't really matter what order we inject them in
122126
await Promise.all(
123127
this.capabilities.receiveState?.map(async ({ eventType, stateKey }) => {
124-
const rawEvents = await this.widgetApi.readStateEvents(eventType, undefined, stateKey);
125-
const events = rawEvents.map(rawEvent => new MatrixEvent(rawEvent));
128+
const rawEvents = await this.widgetApi.readStateEvents(eventType, undefined, stateKey, [this.roomId]);
129+
const events = rawEvents.map(rawEvent => new MatrixEvent(rawEvent as Partial<IEvent>));
126130

127131
await this.syncApi.injectRoomEvents(this.room, [], events);
128132
events.forEach(event => {
@@ -157,8 +161,7 @@ export class RoomWidgetClient extends MatrixClient {
157161
content: any,
158162
stateKey = "",
159163
): Promise<ISendEventResponse> {
160-
if (roomId !== this.roomId) throw new Error(`Can't send events to ${roomId}`);
161-
return await this.widgetApi.sendStateEvent(eventType, stateKey, content);
164+
return await this.widgetApi.sendStateEvent(eventType, stateKey, content, roomId);
162165
}
163166

164167
public async sendToDevice(
@@ -215,11 +218,20 @@ export class RoomWidgetClient extends MatrixClient {
215218

216219
private onEvent = async (ev: CustomEvent<ISendEventToWidgetActionRequest>) => {
217220
ev.preventDefault();
218-
const event = new MatrixEvent(ev.detail.data);
219-
await this.syncApi.injectRoomEvents(this.room, [], [event]);
220-
this.emit(ClientEvent.Event, event);
221-
this.setSyncState(SyncState.Syncing);
222-
logger.info(`Received event ${event.getId()} ${event.getType()} ${event.getStateKey()}`);
221+
222+
// Verify the room ID matches, since it's possible for the client to
223+
// send us events from other rooms if this widget is always on screen
224+
if (ev.detail.data.room_id === this.roomId) {
225+
const event = new MatrixEvent(ev.detail.data as Partial<IEvent>);
226+
await this.syncApi.injectRoomEvents(this.room, [], [event]);
227+
this.emit(ClientEvent.Event, event);
228+
this.setSyncState(SyncState.Syncing);
229+
logger.info(`Received event ${event.getId()} ${event.getType()} ${event.getStateKey()}`);
230+
} else {
231+
const { event_id: eventId, room_id: roomId } = ev.detail.data;
232+
logger.info(`Received event ${eventId} for a different room ${roomId}; discarding`);
233+
}
234+
223235
await this.ack(ev);
224236
};
225237

@@ -229,7 +241,7 @@ export class RoomWidgetClient extends MatrixClient {
229241
const event = new MatrixEvent({
230242
type: ev.detail.data.type,
231243
sender: ev.detail.data.sender,
232-
content: ev.detail.data.content,
244+
content: ev.detail.data.content as IContent,
233245
});
234246
// Mark the event as encrypted if it was, using fake contents and keys since those are unknown to us
235247
if (ev.detail.data.encrypted) event.makeEncrypted(EventType.RoomMessageEncrypted, {}, "", "");

0 commit comments

Comments
 (0)