mirror of https://github.com/vector-im/riot-web
Distinguish room state and timeline events when dealing with widgets
parent
980b922348
commit
f5d96a5ad4
|
@ -6,7 +6,14 @@
|
|||
* 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 {
|
||||
ClientWidgetApi,
|
||||
|
@ -26,7 +33,6 @@ import {
|
|||
WidgetApiFromWidgetAction,
|
||||
WidgetKind,
|
||||
} from "matrix-widget-api";
|
||||
import { Optional } from "matrix-events-sdk";
|
||||
import { EventEmitter } from "events";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
|
@ -56,6 +62,7 @@ import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
|
|||
import Modal from "../../Modal";
|
||||
import ErrorDialog from "../../components/views/dialogs/ErrorDialog";
|
||||
import { SdkContextClass } from "../../contexts/SDKContext";
|
||||
import { UPDATE_EVENT } from "../AsyncStore";
|
||||
|
||||
// TODO: Destroy all of this code
|
||||
|
||||
|
@ -151,6 +158,7 @@ export class StopGapWidget extends EventEmitter {
|
|||
private mockWidget: ElementWidget;
|
||||
private scalarToken?: string;
|
||||
private roomId?: string;
|
||||
private viewedRoomId: string | null = null;
|
||||
private kind: WidgetKind;
|
||||
private readonly virtual: boolean;
|
||||
private readUpToMap: { [roomId: string]: string } = {}; // room ID to event ID
|
||||
|
@ -177,17 +185,6 @@ export class StopGapWidget extends EventEmitter {
|
|||
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 {
|
||||
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.
|
||||
* @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(`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
|
||||
this.messaging.on(`action:${ElementWidgetActions.ViewRoom}`, (ev: CustomEvent<IViewRoomApiRequest>) => {
|
||||
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
|
||||
this.client.on(ClientEvent.Event, this.onEvent);
|
||||
this.client.on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
|
||||
this.client.on(RoomStateEvent.Events, this.onStateUpdate);
|
||||
this.client.on(ClientEvent.ToDeviceEvent, this.onToDeviceEvent);
|
||||
|
||||
this.messaging.on(
|
||||
|
@ -457,8 +475,11 @@ export class StopGapWidget extends EventEmitter {
|
|||
WidgetMessagingStore.instance.stopMessaging(this.mockWidget, this.roomId);
|
||||
this.messaging = null;
|
||||
|
||||
SdkContextClass.instance.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
|
||||
|
||||
this.client.off(ClientEvent.Event, this.onEvent);
|
||||
this.client.off(MatrixEventEvent.Decrypted, this.onEventDecrypted);
|
||||
this.client.off(RoomStateEvent.Events, this.onStateUpdate);
|
||||
this.client.off(ClientEvent.ToDeviceEvent, this.onToDeviceEvent);
|
||||
}
|
||||
|
||||
|
@ -471,6 +492,14 @@ export class StopGapWidget extends EventEmitter {
|
|||
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> => {
|
||||
await this.client.decryptEventIfNeeded(ev);
|
||||
if (ev.isDecryptionFailure()) return;
|
||||
|
@ -570,7 +599,7 @@ export class StopGapWidget extends EventEmitter {
|
|||
this.eventsToFeed.add(ev);
|
||||
} else {
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ import {
|
|||
MatrixCapabilities,
|
||||
OpenIDRequestState,
|
||||
SimpleObservable,
|
||||
Symbols,
|
||||
Widget,
|
||||
WidgetDriver,
|
||||
WidgetEventCapability,
|
||||
|
@ -36,7 +35,6 @@ import {
|
|||
IContent,
|
||||
MatrixError,
|
||||
MatrixEvent,
|
||||
Room,
|
||||
Direction,
|
||||
THREAD_RELATION_TYPE,
|
||||
SendDelayedEventResponse,
|
||||
|
@ -469,70 +467,44 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
|||
}
|
||||
}
|
||||
|
||||
private pickRooms(roomIds?: (string | Symbols.AnyRoom)[]): Room[] {
|
||||
const client = MatrixClientPeg.get();
|
||||
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(
|
||||
public async readRoomTimeline(
|
||||
roomId: string,
|
||||
eventType: string,
|
||||
msgtype: string | undefined,
|
||||
limitPerRoom: number,
|
||||
roomIds?: (string | Symbols.AnyRoom)[],
|
||||
stateKey: string | undefined,
|
||||
limit: number,
|
||||
since: string | undefined,
|
||||
): 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 allResults: IRoomEvent[] = [];
|
||||
for (const room of rooms) {
|
||||
const results: MatrixEvent[] = [];
|
||||
const events = room.getLiveTimeline().getEvents(); // timelines are most recent last
|
||||
for (let i = events.length - 1; i > 0; i--) {
|
||||
if (results.length >= limitPerRoom) break;
|
||||
const room = MatrixClientPeg.safeGet().getRoom(roomId);
|
||||
if (room === null) return [];
|
||||
const results: MatrixEvent[] = [];
|
||||
const events = room.getLiveTimeline().getEvents(); // timelines are most recent last
|
||||
for (let i = events.length - 1; i > 0; i--) {
|
||||
const ev = events[i];
|
||||
if (results.length >= limit) break;
|
||||
if (since !== undefined && ev.getId() === since) break;
|
||||
|
||||
const ev = events[i];
|
||||
if (ev.getType() !== eventType || ev.isState()) continue;
|
||||
if (eventType === EventType.RoomMessage && msgtype && msgtype !== ev.getContent()["msgtype"]) continue;
|
||||
results.push(ev);
|
||||
}
|
||||
|
||||
results.forEach((e) => allResults.push(e.getEffectiveEvent() as IRoomEvent));
|
||||
if (ev.getType() !== eventType || ev.isState()) continue;
|
||||
if (eventType === EventType.RoomMessage && msgtype && msgtype !== ev.getContent()["msgtype"]) continue;
|
||||
if (ev.getStateKey() !== undefined && stateKey !== undefined && ev.getStateKey() !== stateKey) continue;
|
||||
results.push(ev);
|
||||
}
|
||||
return allResults;
|
||||
|
||||
return results.map((e) => e.getEffectiveEvent() as IRoomEvent);
|
||||
}
|
||||
|
||||
public async readStateEvents(
|
||||
eventType: string,
|
||||
stateKey: string | undefined,
|
||||
limitPerRoom: number,
|
||||
roomIds?: (string | Symbols.AnyRoom)[],
|
||||
): Promise<IRoomEvent[]> {
|
||||
limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary
|
||||
public async readRoomState(roomId: string, eventType: string, stateKey: string | undefined): Promise<IRoomEvent[]> {
|
||||
const room = MatrixClientPeg.safeGet().getRoom(roomId);
|
||||
if (room === null) return [];
|
||||
const state = room.getLiveTimeline().getState(Direction.Forward);
|
||||
if (state === undefined) return [];
|
||||
|
||||
const rooms = this.pickRooms(roomIds);
|
||||
const allResults: IRoomEvent[] = [];
|
||||
for (const room of rooms) {
|
||||
const results: MatrixEvent[] = [];
|
||||
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;
|
||||
if (stateKey === undefined)
|
||||
return state.getStateEvents(eventType).map((e) => e.getEffectiveEvent() as IRoomEvent);
|
||||
const event = state.getStateEvents(eventType, stateKey);
|
||||
return event === null ? [] : [event.getEffectiveEvent() as IRoomEvent];
|
||||
}
|
||||
|
||||
public async askOpenID(observer: SimpleObservable<IOpenIDUpdate>): Promise<void> {
|
||||
|
@ -693,6 +665,12 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
|||
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
|
||||
* for use by Widget API error responses.
|
||||
|
|
Loading…
Reference in New Issue