Distinguish room state and timeline events when dealing with widgets

pull/28681/head
Robin 2024-12-06 16:04:05 -05:00
parent 980b922348
commit f5d96a5ad4
2 changed files with 78 additions and 71 deletions

View File

@ -6,7 +6,14 @@
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
*/ */
import { Room, MatrixEvent, MatrixEventEvent, MatrixClient, ClientEvent } from "matrix-js-sdk/src/matrix"; import {
Room,
MatrixEvent,
MatrixEventEvent,
MatrixClient,
ClientEvent,
RoomStateEvent,
} from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types"; import { KnownMembership } from "matrix-js-sdk/src/types";
import { import {
ClientWidgetApi, ClientWidgetApi,
@ -26,7 +33,6 @@ import {
WidgetApiFromWidgetAction, WidgetApiFromWidgetAction,
WidgetKind, WidgetKind,
} from "matrix-widget-api"; } from "matrix-widget-api";
import { Optional } from "matrix-events-sdk";
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
@ -56,6 +62,7 @@ import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
import Modal from "../../Modal"; import Modal from "../../Modal";
import ErrorDialog from "../../components/views/dialogs/ErrorDialog"; import ErrorDialog from "../../components/views/dialogs/ErrorDialog";
import { SdkContextClass } from "../../contexts/SDKContext"; import { SdkContextClass } from "../../contexts/SDKContext";
import { UPDATE_EVENT } from "../AsyncStore";
// TODO: Destroy all of this code // TODO: Destroy all of this code
@ -151,6 +158,7 @@ export class StopGapWidget extends EventEmitter {
private mockWidget: ElementWidget; private mockWidget: ElementWidget;
private scalarToken?: string; private scalarToken?: string;
private roomId?: string; private roomId?: string;
private viewedRoomId: string | null = null;
private kind: WidgetKind; private kind: WidgetKind;
private readonly virtual: boolean; private readonly virtual: boolean;
private readUpToMap: { [roomId: string]: string } = {}; // room ID to event ID private readUpToMap: { [roomId: string]: string } = {}; // room ID to event ID
@ -177,17 +185,6 @@ export class StopGapWidget extends EventEmitter {
this.stickyPromise = appTileProps.stickyPromise; this.stickyPromise = appTileProps.stickyPromise;
} }
private get eventListenerRoomId(): Optional<string> {
// When widgets are listening to events, we need to make sure they're only
// receiving events for the right room. In particular, room widgets get locked
// to the room they were added in while account widgets listen to the currently
// active room.
if (this.roomId) return this.roomId;
return SdkContextClass.instance.roomViewStore.getRoomId();
}
public get widgetApi(): ClientWidgetApi | null { public get widgetApi(): ClientWidgetApi | null {
return this.messaging; return this.messaging;
} }
@ -259,6 +256,15 @@ export class StopGapWidget extends EventEmitter {
}); });
} }
}; };
private onRoomViewStoreUpdate = (): void => {
const roomId = SdkContextClass.instance.roomViewStore.getRoomId() ?? null;
if (roomId !== this.viewedRoomId) {
this.messaging!.setViewedRoomId(roomId);
this.viewedRoomId = roomId;
}
};
/** /**
* This starts the messaging for the widget if it is not in the state `started` yet. * This starts the messaging for the widget if it is not in the state `started` yet.
* @param iframe the iframe the widget should use * @param iframe the iframe the widget should use
@ -285,6 +291,17 @@ export class StopGapWidget extends EventEmitter {
this.messaging.on("capabilitiesNotified", () => this.emit("capabilitiesNotified")); this.messaging.on("capabilitiesNotified", () => this.emit("capabilitiesNotified"));
this.messaging.on(`action:${WidgetApiFromWidgetAction.OpenModalWidget}`, this.onOpenModal); this.messaging.on(`action:${WidgetApiFromWidgetAction.OpenModalWidget}`, this.onOpenModal);
// When widgets are listening to events, we need to make sure they're only
// receiving events for the right room
if (this.roomId === undefined) {
// Account widgets listen to the currently active room
this.messaging.setViewedRoomId(SdkContextClass.instance.roomViewStore.getRoomId() ?? null);
SdkContextClass.instance.roomViewStore.on(UPDATE_EVENT, this.onRoomViewStoreUpdate);
} else {
// Room widgets get looked to the room they were added in
this.messaging.setViewedRoomId(this.roomId);
}
// Always attach a handler for ViewRoom, but permission check it internally // Always attach a handler for ViewRoom, but permission check it internally
this.messaging.on(`action:${ElementWidgetActions.ViewRoom}`, (ev: CustomEvent<IViewRoomApiRequest>) => { this.messaging.on(`action:${ElementWidgetActions.ViewRoom}`, (ev: CustomEvent<IViewRoomApiRequest>) => {
ev.preventDefault(); // stop the widget API from auto-rejecting this ev.preventDefault(); // stop the widget API from auto-rejecting this
@ -329,6 +346,7 @@ export class StopGapWidget extends EventEmitter {
// Attach listeners for feeding events - the underlying widget classes handle permissions for us // Attach listeners for feeding events - the underlying widget classes handle permissions for us
this.client.on(ClientEvent.Event, this.onEvent); this.client.on(ClientEvent.Event, this.onEvent);
this.client.on(MatrixEventEvent.Decrypted, this.onEventDecrypted); this.client.on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
this.client.on(RoomStateEvent.Events, this.onStateUpdate);
this.client.on(ClientEvent.ToDeviceEvent, this.onToDeviceEvent); this.client.on(ClientEvent.ToDeviceEvent, this.onToDeviceEvent);
this.messaging.on( this.messaging.on(
@ -457,8 +475,11 @@ export class StopGapWidget extends EventEmitter {
WidgetMessagingStore.instance.stopMessaging(this.mockWidget, this.roomId); WidgetMessagingStore.instance.stopMessaging(this.mockWidget, this.roomId);
this.messaging = null; this.messaging = null;
SdkContextClass.instance.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
this.client.off(ClientEvent.Event, this.onEvent); this.client.off(ClientEvent.Event, this.onEvent);
this.client.off(MatrixEventEvent.Decrypted, this.onEventDecrypted); this.client.off(MatrixEventEvent.Decrypted, this.onEventDecrypted);
this.client.off(RoomStateEvent.Events, this.onStateUpdate);
this.client.off(ClientEvent.ToDeviceEvent, this.onToDeviceEvent); this.client.off(ClientEvent.ToDeviceEvent, this.onToDeviceEvent);
} }
@ -471,6 +492,14 @@ export class StopGapWidget extends EventEmitter {
this.feedEvent(ev); this.feedEvent(ev);
}; };
private onStateUpdate = (ev: MatrixEvent): void => {
if (this.messaging === null) return;
const raw = ev.getEffectiveEvent();
this.messaging.feedStateUpdate(raw as IRoomEvent).catch((e) => {
logger.error("Error sending state update to widget: ", e);
});
};
private onToDeviceEvent = async (ev: MatrixEvent): Promise<void> => { private onToDeviceEvent = async (ev: MatrixEvent): Promise<void> => {
await this.client.decryptEventIfNeeded(ev); await this.client.decryptEventIfNeeded(ev);
if (ev.isDecryptionFailure()) return; if (ev.isDecryptionFailure()) return;
@ -570,7 +599,7 @@ export class StopGapWidget extends EventEmitter {
this.eventsToFeed.add(ev); this.eventsToFeed.add(ev);
} else { } else {
const raw = ev.getEffectiveEvent(); const raw = ev.getEffectiveEvent();
this.messaging.feedEvent(raw as IRoomEvent, this.eventListenerRoomId!).catch((e) => { this.messaging.feedEvent(raw as IRoomEvent).catch((e) => {
logger.error("Error sending event to widget: ", e); logger.error("Error sending event to widget: ", e);
}); });
} }

View File

@ -19,7 +19,6 @@ import {
MatrixCapabilities, MatrixCapabilities,
OpenIDRequestState, OpenIDRequestState,
SimpleObservable, SimpleObservable,
Symbols,
Widget, Widget,
WidgetDriver, WidgetDriver,
WidgetEventCapability, WidgetEventCapability,
@ -36,7 +35,6 @@ import {
IContent, IContent,
MatrixError, MatrixError,
MatrixEvent, MatrixEvent,
Room,
Direction, Direction,
THREAD_RELATION_TYPE, THREAD_RELATION_TYPE,
SendDelayedEventResponse, SendDelayedEventResponse,
@ -469,70 +467,44 @@ export class StopGapWidgetDriver extends WidgetDriver {
} }
} }
private pickRooms(roomIds?: (string | Symbols.AnyRoom)[]): Room[] { public async readRoomTimeline(
const client = MatrixClientPeg.get(); roomId: string,
if (!client) throw new Error("Not attached to a client");
const targetRooms = roomIds
? roomIds.includes(Symbols.AnyRoom)
? client.getVisibleRooms(SettingsStore.getValue("feature_dynamic_room_predecessors"))
: roomIds.map((r) => client.getRoom(r))
: [client.getRoom(SdkContextClass.instance.roomViewStore.getRoomId()!)];
return targetRooms.filter((r) => !!r) as Room[];
}
public async readRoomEvents(
eventType: string, eventType: string,
msgtype: string | undefined, msgtype: string | undefined,
limitPerRoom: number, stateKey: string | undefined,
roomIds?: (string | Symbols.AnyRoom)[], limit: number,
since: string | undefined,
): Promise<IRoomEvent[]> { ): Promise<IRoomEvent[]> {
limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary limit = limit > 0 ? Math.min(limit, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary
const rooms = this.pickRooms(roomIds); const room = MatrixClientPeg.safeGet().getRoom(roomId);
const allResults: IRoomEvent[] = []; if (room === null) return [];
for (const room of rooms) { const results: MatrixEvent[] = [];
const results: MatrixEvent[] = []; const events = room.getLiveTimeline().getEvents(); // timelines are most recent last
const events = room.getLiveTimeline().getEvents(); // timelines are most recent last for (let i = events.length - 1; i > 0; i--) {
for (let i = events.length - 1; i > 0; i--) { const ev = events[i];
if (results.length >= limitPerRoom) break; if (results.length >= limit) break;
if (since !== undefined && ev.getId() === since) break;
const ev = events[i]; if (ev.getType() !== eventType || ev.isState()) continue;
if (ev.getType() !== eventType || ev.isState()) continue; if (eventType === EventType.RoomMessage && msgtype && msgtype !== ev.getContent()["msgtype"]) continue;
if (eventType === EventType.RoomMessage && msgtype && msgtype !== ev.getContent()["msgtype"]) continue; if (ev.getStateKey() !== undefined && stateKey !== undefined && ev.getStateKey() !== stateKey) continue;
results.push(ev); results.push(ev);
}
results.forEach((e) => allResults.push(e.getEffectiveEvent() as IRoomEvent));
} }
return allResults;
return results.map((e) => e.getEffectiveEvent() as IRoomEvent);
} }
public async readStateEvents( public async readRoomState(roomId: string, eventType: string, stateKey: string | undefined): Promise<IRoomEvent[]> {
eventType: string, const room = MatrixClientPeg.safeGet().getRoom(roomId);
stateKey: string | undefined, if (room === null) return [];
limitPerRoom: number, const state = room.getLiveTimeline().getState(Direction.Forward);
roomIds?: (string | Symbols.AnyRoom)[], if (state === undefined) return [];
): Promise<IRoomEvent[]> {
limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary
const rooms = this.pickRooms(roomIds); if (stateKey === undefined)
const allResults: IRoomEvent[] = []; return state.getStateEvents(eventType).map((e) => e.getEffectiveEvent() as IRoomEvent);
for (const room of rooms) { const event = state.getStateEvents(eventType, stateKey);
const results: MatrixEvent[] = []; return event === null ? [] : [event.getEffectiveEvent() as IRoomEvent];
const state = room.currentState.events.get(eventType);
if (state) {
if (stateKey === "" || !!stateKey) {
const forKey = state.get(stateKey);
if (forKey) results.push(forKey);
} else {
results.push(...Array.from(state.values()));
}
}
results.slice(0, limitPerRoom).forEach((e) => allResults.push(e.getEffectiveEvent() as IRoomEvent));
}
return allResults;
} }
public async askOpenID(observer: SimpleObservable<IOpenIDUpdate>): Promise<void> { public async askOpenID(observer: SimpleObservable<IOpenIDUpdate>): Promise<void> {
@ -693,6 +665,12 @@ export class StopGapWidgetDriver extends WidgetDriver {
return { file: blob }; return { file: blob };
} }
public getKnownRooms(): string[] {
return MatrixClientPeg.safeGet()
.getVisibleRooms(SettingsStore.getValue("feature_dynamic_room_predecessors"))
.map((r) => r.roomId);
}
/** /**
* Expresses a {@link MatrixError} as a JSON payload * Expresses a {@link MatrixError} as a JSON payload
* for use by Widget API error responses. * for use by Widget API error responses.