Skip to content

Commit d3a7b16

Browse files
authored
Merge branch 'develop' into florianduros/devtools-crypto
2 parents 961a156 + 9a109cd commit d3a7b16

File tree

5 files changed

+301
-89
lines changed

5 files changed

+301
-89
lines changed
Binary file not shown.

src/stores/widgets/StopGapWidget.ts

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@
66
* Please see LICENSE files in the repository root for full details.
77
*/
88

9-
import { Room, MatrixEvent, MatrixEventEvent, MatrixClient, ClientEvent } from "matrix-js-sdk/src/matrix";
9+
import {
10+
Room,
11+
MatrixEvent,
12+
MatrixEventEvent,
13+
MatrixClient,
14+
ClientEvent,
15+
RoomStateEvent,
16+
} from "matrix-js-sdk/src/matrix";
1017
import { KnownMembership } from "matrix-js-sdk/src/types";
1118
import {
1219
ClientWidgetApi,
@@ -26,7 +33,6 @@ import {
2633
WidgetApiFromWidgetAction,
2734
WidgetKind,
2835
} from "matrix-widget-api";
29-
import { Optional } from "matrix-events-sdk";
3036
import { EventEmitter } from "events";
3137
import { logger } from "matrix-js-sdk/src/logger";
3238

@@ -56,6 +62,7 @@ import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
5662
import Modal from "../../Modal";
5763
import ErrorDialog from "../../components/views/dialogs/ErrorDialog";
5864
import { SdkContextClass } from "../../contexts/SDKContext";
65+
import { UPDATE_EVENT } from "../AsyncStore";
5966

6067
// TODO: Destroy all of this code
6168

@@ -151,6 +158,9 @@ export class StopGapWidget extends EventEmitter {
151158
private mockWidget: ElementWidget;
152159
private scalarToken?: string;
153160
private roomId?: string;
161+
// The room that we're currently allowing the widget to interact with. Only
162+
// used for account widgets, which may follow the user to different rooms.
163+
private viewedRoomId: string | null = null;
154164
private kind: WidgetKind;
155165
private readonly virtual: boolean;
156166
private readUpToMap: { [roomId: string]: string } = {}; // room ID to event ID
@@ -177,17 +187,6 @@ export class StopGapWidget extends EventEmitter {
177187
this.stickyPromise = appTileProps.stickyPromise;
178188
}
179189

180-
private get eventListenerRoomId(): Optional<string> {
181-
// When widgets are listening to events, we need to make sure they're only
182-
// receiving events for the right room. In particular, room widgets get locked
183-
// to the room they were added in while account widgets listen to the currently
184-
// active room.
185-
186-
if (this.roomId) return this.roomId;
187-
188-
return SdkContextClass.instance.roomViewStore.getRoomId();
189-
}
190-
191190
public get widgetApi(): ClientWidgetApi | null {
192191
return this.messaging;
193192
}
@@ -259,6 +258,17 @@ export class StopGapWidget extends EventEmitter {
259258
});
260259
}
261260
};
261+
262+
// This listener is only active for account widgets, which may follow the
263+
// user to different rooms
264+
private onRoomViewStoreUpdate = (): void => {
265+
const roomId = SdkContextClass.instance.roomViewStore.getRoomId() ?? null;
266+
if (roomId !== this.viewedRoomId) {
267+
this.messaging!.setViewedRoomId(roomId);
268+
this.viewedRoomId = roomId;
269+
}
270+
};
271+
262272
/**
263273
* This starts the messaging for the widget if it is not in the state `started` yet.
264274
* @param iframe the iframe the widget should use
@@ -285,6 +295,17 @@ export class StopGapWidget extends EventEmitter {
285295
this.messaging.on("capabilitiesNotified", () => this.emit("capabilitiesNotified"));
286296
this.messaging.on(`action:${WidgetApiFromWidgetAction.OpenModalWidget}`, this.onOpenModal);
287297

298+
// When widgets are listening to events, we need to make sure they're only
299+
// receiving events for the right room
300+
if (this.roomId === undefined) {
301+
// Account widgets listen to the currently active room
302+
this.messaging.setViewedRoomId(SdkContextClass.instance.roomViewStore.getRoomId() ?? null);
303+
SdkContextClass.instance.roomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate);
304+
} else {
305+
// Room widgets get locked to the room they were added in
306+
this.messaging.setViewedRoomId(this.roomId);
307+
}
308+
288309
// Always attach a handler for ViewRoom, but permission check it internally
289310
this.messaging.on(`action:${ElementWidgetActions.ViewRoom}`, (ev: CustomEvent<IViewRoomApiRequest>) => {
290311
ev.preventDefault(); // stop the widget API from auto-rejecting this
@@ -329,6 +350,7 @@ export class StopGapWidget extends EventEmitter {
329350
// Attach listeners for feeding events - the underlying widget classes handle permissions for us
330351
this.client.on(ClientEvent.Event, this.onEvent);
331352
this.client.on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
353+
this.client.on(RoomStateEvent.Events, this.onStateUpdate);
332354
this.client.on(ClientEvent.ToDeviceEvent, this.onToDeviceEvent);
333355

334356
this.messaging.on(
@@ -457,8 +479,11 @@ export class StopGapWidget extends EventEmitter {
457479
WidgetMessagingStore.instance.stopMessaging(this.mockWidget, this.roomId);
458480
this.messaging = null;
459481

482+
SdkContextClass.instance.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
483+
460484
this.client.off(ClientEvent.Event, this.onEvent);
461485
this.client.off(MatrixEventEvent.Decrypted, this.onEventDecrypted);
486+
this.client.off(RoomStateEvent.Events, this.onStateUpdate);
462487
this.client.off(ClientEvent.ToDeviceEvent, this.onToDeviceEvent);
463488
}
464489

@@ -471,6 +496,14 @@ export class StopGapWidget extends EventEmitter {
471496
this.feedEvent(ev);
472497
};
473498

499+
private onStateUpdate = (ev: MatrixEvent): void => {
500+
if (this.messaging === null) return;
501+
const raw = ev.getEffectiveEvent();
502+
this.messaging.feedStateUpdate(raw as IRoomEvent).catch((e) => {
503+
logger.error("Error sending state update to widget: ", e);
504+
});
505+
};
506+
474507
private onToDeviceEvent = async (ev: MatrixEvent): Promise<void> => {
475508
await this.client.decryptEventIfNeeded(ev);
476509
if (ev.isDecryptionFailure()) return;
@@ -570,7 +603,7 @@ export class StopGapWidget extends EventEmitter {
570603
this.eventsToFeed.add(ev);
571604
} else {
572605
const raw = ev.getEffectiveEvent();
573-
this.messaging.feedEvent(raw as IRoomEvent, this.eventListenerRoomId!).catch((e) => {
606+
this.messaging.feedEvent(raw as IRoomEvent).catch((e) => {
574607
logger.error("Error sending event to widget: ", e);
575608
});
576609
}

src/stores/widgets/StopGapWidgetDriver.ts

Lines changed: 68 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import {
1919
MatrixCapabilities,
2020
OpenIDRequestState,
2121
SimpleObservable,
22-
Symbols,
2322
Widget,
2423
WidgetDriver,
2524
WidgetEventCapability,
@@ -36,7 +35,6 @@ import {
3635
IContent,
3736
MatrixError,
3837
MatrixEvent,
39-
Room,
4038
Direction,
4139
THREAD_RELATION_TYPE,
4240
SendDelayedEventResponse,
@@ -469,70 +467,69 @@ export class StopGapWidgetDriver extends WidgetDriver {
469467
}
470468
}
471469

472-
private pickRooms(roomIds?: (string | Symbols.AnyRoom)[]): Room[] {
473-
const client = MatrixClientPeg.get();
474-
if (!client) throw new Error("Not attached to a client");
475-
476-
const targetRooms = roomIds
477-
? roomIds.includes(Symbols.AnyRoom)
478-
? client.getVisibleRooms(SettingsStore.getValue("feature_dynamic_room_predecessors"))
479-
: roomIds.map((r) => client.getRoom(r))
480-
: [client.getRoom(SdkContextClass.instance.roomViewStore.getRoomId()!)];
481-
return targetRooms.filter((r) => !!r) as Room[];
482-
}
483-
484-
public async readRoomEvents(
470+
/**
471+
* Reads all events of the given type, and optionally `msgtype` (if applicable/defined),
472+
* the user has access to. The widget API will have already verified that the widget is
473+
* capable of receiving the events. Less events than the limit are allowed to be returned,
474+
* but not more.
475+
* @param roomId The ID of the room to look within.
476+
* @param eventType The event type to be read.
477+
* @param msgtype The msgtype of the events to be read, if applicable/defined.
478+
* @param stateKey The state key of the events to be read, if applicable/defined.
479+
* @param limit The maximum number of events to retrieve. Will be zero to denote "as many as
480+
* possible".
481+
* @param since When null, retrieves the number of events specified by the "limit" parameter.
482+
* Otherwise, the event ID at which only subsequent events will be returned, as many as specified
483+
* in "limit".
484+
* @returns {Promise<IRoomEvent[]>} Resolves to the room events, or an empty array.
485+
*/
486+
public async readRoomTimeline(
487+
roomId: string,
485488
eventType: string,
486489
msgtype: string | undefined,
487-
limitPerRoom: number,
488-
roomIds?: (string | Symbols.AnyRoom)[],
490+
stateKey: string | undefined,
491+
limit: number,
492+
since: string | undefined,
489493
): Promise<IRoomEvent[]> {
490-
limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary
491-
492-
const rooms = this.pickRooms(roomIds);
493-
const allResults: IRoomEvent[] = [];
494-
for (const room of rooms) {
495-
const results: MatrixEvent[] = [];
496-
const events = room.getLiveTimeline().getEvents(); // timelines are most recent last
497-
for (let i = events.length - 1; i > 0; i--) {
498-
if (results.length >= limitPerRoom) break;
499-
500-
const ev = events[i];
501-
if (ev.getType() !== eventType || ev.isState()) continue;
502-
if (eventType === EventType.RoomMessage && msgtype && msgtype !== ev.getContent()["msgtype"]) continue;
503-
results.push(ev);
504-
}
505-
506-
results.forEach((e) => allResults.push(e.getEffectiveEvent() as IRoomEvent));
494+
limit = limit > 0 ? Math.min(limit, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary
495+
496+
const room = MatrixClientPeg.safeGet().getRoom(roomId);
497+
if (room === null) return [];
498+
const results: MatrixEvent[] = [];
499+
const events = room.getLiveTimeline().getEvents(); // timelines are most recent last
500+
for (let i = events.length - 1; i >= 0; i--) {
501+
const ev = events[i];
502+
if (results.length >= limit) break;
503+
if (since !== undefined && ev.getId() === since) break;
504+
505+
if (ev.getType() !== eventType || ev.isState()) continue;
506+
if (eventType === EventType.RoomMessage && msgtype && msgtype !== ev.getContent()["msgtype"]) continue;
507+
if (ev.getStateKey() !== undefined && stateKey !== undefined && ev.getStateKey() !== stateKey) continue;
508+
results.push(ev);
507509
}
508-
return allResults;
509-
}
510510

511-
public async readStateEvents(
512-
eventType: string,
513-
stateKey: string | undefined,
514-
limitPerRoom: number,
515-
roomIds?: (string | Symbols.AnyRoom)[],
516-
): Promise<IRoomEvent[]> {
517-
limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary
518-
519-
const rooms = this.pickRooms(roomIds);
520-
const allResults: IRoomEvent[] = [];
521-
for (const room of rooms) {
522-
const results: MatrixEvent[] = [];
523-
const state = room.currentState.events.get(eventType);
524-
if (state) {
525-
if (stateKey === "" || !!stateKey) {
526-
const forKey = state.get(stateKey);
527-
if (forKey) results.push(forKey);
528-
} else {
529-
results.push(...Array.from(state.values()));
530-
}
531-
}
511+
return results.map((e) => e.getEffectiveEvent() as IRoomEvent);
512+
}
532513

533-
results.slice(0, limitPerRoom).forEach((e) => allResults.push(e.getEffectiveEvent() as IRoomEvent));
534-
}
535-
return allResults;
514+
/**
515+
* Reads the current values of all matching room state entries.
516+
* @param roomId The ID of the room.
517+
* @param eventType The event type of the entries to be read.
518+
* @param stateKey The state key of the entry to be read. If undefined,
519+
* all room state entries with a matching event type should be returned.
520+
* @returns {Promise<IRoomEvent[]>} Resolves to the events representing the
521+
* current values of the room state entries.
522+
*/
523+
public async readRoomState(roomId: string, eventType: string, stateKey: string | undefined): Promise<IRoomEvent[]> {
524+
const room = MatrixClientPeg.safeGet().getRoom(roomId);
525+
if (room === null) return [];
526+
const state = room.getLiveTimeline().getState(Direction.Forward);
527+
if (state === undefined) return [];
528+
529+
if (stateKey === undefined)
530+
return state.getStateEvents(eventType).map((e) => e.getEffectiveEvent() as IRoomEvent);
531+
const event = state.getStateEvents(eventType, stateKey);
532+
return event === null ? [] : [event.getEffectiveEvent() as IRoomEvent];
536533
}
537534

538535
public async askOpenID(observer: SimpleObservable<IOpenIDUpdate>): Promise<void> {
@@ -693,6 +690,17 @@ export class StopGapWidgetDriver extends WidgetDriver {
693690
return { file: blob };
694691
}
695692

693+
/**
694+
* Gets the IDs of all joined or invited rooms currently known to the
695+
* client.
696+
* @returns The room IDs.
697+
*/
698+
public getKnownRooms(): string[] {
699+
return MatrixClientPeg.safeGet()
700+
.getVisibleRooms(SettingsStore.getValue("feature_dynamic_room_predecessors"))
701+
.map((r) => r.roomId);
702+
}
703+
696704
/**
697705
* Expresses a {@link MatrixError} as a JSON payload
698706
* for use by Widget API error responses.

0 commit comments

Comments
 (0)