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.
|
* 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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue