mirror of
https://github.com/element-hq/element-web.git
synced 2025-12-11 01:40:42 +00:00
Compare commits
2 Commits
floriandur
...
robin/reve
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63d32efb27 | ||
|
|
f05df80b46 |
@@ -128,7 +128,7 @@
|
|||||||
"matrix-encrypt-attachment": "^1.0.3",
|
"matrix-encrypt-attachment": "^1.0.3",
|
||||||
"matrix-events-sdk": "0.0.1",
|
"matrix-events-sdk": "0.0.1",
|
||||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
||||||
"matrix-widget-api": "^1.10.0",
|
"matrix-widget-api": "1.11.0",
|
||||||
"memoize-one": "^6.0.0",
|
"memoize-one": "^6.0.0",
|
||||||
"mime": "^4.0.4",
|
"mime": "^4.0.4",
|
||||||
"oidc-client-ts": "^3.0.1",
|
"oidc-client-ts": "^3.0.1",
|
||||||
|
|||||||
@@ -6,14 +6,7 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { Room, MatrixEvent, MatrixEventEvent, MatrixClient, ClientEvent } from "matrix-js-sdk/src/matrix";
|
||||||
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,
|
||||||
@@ -33,6 +26,7 @@ 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";
|
||||||
|
|
||||||
@@ -62,7 +56,6 @@ 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
|
||||||
|
|
||||||
@@ -158,9 +151,6 @@ export class StopGapWidget extends EventEmitter {
|
|||||||
private mockWidget: ElementWidget;
|
private mockWidget: ElementWidget;
|
||||||
private scalarToken?: string;
|
private scalarToken?: string;
|
||||||
private roomId?: string;
|
private roomId?: string;
|
||||||
// The room that we're currently allowing the widget to interact with. Only
|
|
||||||
// used for account widgets, which may follow the user to different rooms.
|
|
||||||
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
|
||||||
@@ -187,6 +177,17 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -258,17 +259,6 @@ export class StopGapWidget extends EventEmitter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// This listener is only active for account widgets, which may follow the
|
|
||||||
// user to different rooms
|
|
||||||
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
|
||||||
@@ -295,17 +285,6 @@ 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 locked 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
|
||||||
@@ -350,7 +329,6 @@ 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(
|
||||||
@@ -479,11 +457,8 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -496,14 +471,6 @@ 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;
|
||||||
@@ -603,7 +570,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).catch((e) => {
|
this.messaging.feedEvent(raw as IRoomEvent, this.eventListenerRoomId!).catch((e) => {
|
||||||
logger.error("Error sending event to widget: ", e);
|
logger.error("Error sending event to widget: ", e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
MatrixCapabilities,
|
MatrixCapabilities,
|
||||||
OpenIDRequestState,
|
OpenIDRequestState,
|
||||||
SimpleObservable,
|
SimpleObservable,
|
||||||
|
Symbols,
|
||||||
Widget,
|
Widget,
|
||||||
WidgetDriver,
|
WidgetDriver,
|
||||||
WidgetEventCapability,
|
WidgetEventCapability,
|
||||||
@@ -35,6 +36,7 @@ import {
|
|||||||
IContent,
|
IContent,
|
||||||
MatrixError,
|
MatrixError,
|
||||||
MatrixEvent,
|
MatrixEvent,
|
||||||
|
Room,
|
||||||
Direction,
|
Direction,
|
||||||
THREAD_RELATION_TYPE,
|
THREAD_RELATION_TYPE,
|
||||||
SendDelayedEventResponse,
|
SendDelayedEventResponse,
|
||||||
@@ -467,69 +469,70 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private pickRooms(roomIds?: (string | Symbols.AnyRoom)[]): Room[] {
|
||||||
* Reads all events of the given type, and optionally `msgtype` (if applicable/defined),
|
const client = MatrixClientPeg.get();
|
||||||
* the user has access to. The widget API will have already verified that the widget is
|
if (!client) throw new Error("Not attached to a client");
|
||||||
* capable of receiving the events. Less events than the limit are allowed to be returned,
|
|
||||||
* but not more.
|
|
||||||
* @param roomId The ID of the room to look within.
|
|
||||||
* @param eventType The event type to be read.
|
|
||||||
* @param msgtype The msgtype of the events to be read, if applicable/defined.
|
|
||||||
* @param stateKey The state key of the events to be read, if applicable/defined.
|
|
||||||
* @param limit The maximum number of events to retrieve. Will be zero to denote "as many as
|
|
||||||
* possible".
|
|
||||||
* @param since When null, retrieves the number of events specified by the "limit" parameter.
|
|
||||||
* Otherwise, the event ID at which only subsequent events will be returned, as many as specified
|
|
||||||
* in "limit".
|
|
||||||
* @returns {Promise<IRoomEvent[]>} Resolves to the room events, or an empty array.
|
|
||||||
*/
|
|
||||||
public async readRoomTimeline(
|
|
||||||
roomId: string,
|
|
||||||
eventType: string,
|
|
||||||
msgtype: string | undefined,
|
|
||||||
stateKey: string | undefined,
|
|
||||||
limit: number,
|
|
||||||
since: string | undefined,
|
|
||||||
): Promise<IRoomEvent[]> {
|
|
||||||
limit = limit > 0 ? Math.min(limit, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary
|
|
||||||
|
|
||||||
const room = MatrixClientPeg.safeGet().getRoom(roomId);
|
const targetRooms = roomIds
|
||||||
if (room === null) return [];
|
? roomIds.includes(Symbols.AnyRoom)
|
||||||
const results: MatrixEvent[] = [];
|
? client.getVisibleRooms(SettingsStore.getValue("feature_dynamic_room_predecessors"))
|
||||||
const events = room.getLiveTimeline().getEvents(); // timelines are most recent last
|
: roomIds.map((r) => client.getRoom(r))
|
||||||
for (let i = events.length - 1; i >= 0; i--) {
|
: [client.getRoom(SdkContextClass.instance.roomViewStore.getRoomId()!)];
|
||||||
const ev = events[i];
|
return targetRooms.filter((r) => !!r) as Room[];
|
||||||
if (results.length >= limit) break;
|
|
||||||
if (since !== undefined && ev.getId() === since) break;
|
|
||||||
|
|
||||||
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 results.map((e) => e.getEffectiveEvent() as IRoomEvent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public async readRoomEvents(
|
||||||
* Reads the current values of all matching room state entries.
|
eventType: string,
|
||||||
* @param roomId The ID of the room.
|
msgtype: string | undefined,
|
||||||
* @param eventType The event type of the entries to be read.
|
limitPerRoom: number,
|
||||||
* @param stateKey The state key of the entry to be read. If undefined,
|
roomIds?: (string | Symbols.AnyRoom)[],
|
||||||
* all room state entries with a matching event type should be returned.
|
): Promise<IRoomEvent[]> {
|
||||||
* @returns {Promise<IRoomEvent[]>} Resolves to the events representing the
|
limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary
|
||||||
* current values of the room state entries.
|
|
||||||
*/
|
|
||||||
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 [];
|
|
||||||
|
|
||||||
if (stateKey === undefined)
|
const rooms = this.pickRooms(roomIds);
|
||||||
return state.getStateEvents(eventType).map((e) => e.getEffectiveEvent() as IRoomEvent);
|
const allResults: IRoomEvent[] = [];
|
||||||
const event = state.getStateEvents(eventType, stateKey);
|
for (const room of rooms) {
|
||||||
return event === null ? [] : [event.getEffectiveEvent() as IRoomEvent];
|
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 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));
|
||||||
|
}
|
||||||
|
return allResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async askOpenID(observer: SimpleObservable<IOpenIDUpdate>): Promise<void> {
|
public async askOpenID(observer: SimpleObservable<IOpenIDUpdate>): Promise<void> {
|
||||||
@@ -690,17 +693,6 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
|||||||
return { file: blob };
|
return { file: blob };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the IDs of all joined or invited rooms currently known to the
|
|
||||||
* client.
|
|
||||||
* @returns The room IDs.
|
|
||||||
*/
|
|
||||||
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.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
|||||||
Please see LICENSE files in the repository root for full details.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { mocked, MockedFunction, MockedObject } from "jest-mock";
|
import { mocked, MockedObject } from "jest-mock";
|
||||||
import { last } from "lodash";
|
import { last } from "lodash";
|
||||||
import {
|
import {
|
||||||
MatrixEvent,
|
MatrixEvent,
|
||||||
@@ -15,20 +15,15 @@ import {
|
|||||||
EventTimeline,
|
EventTimeline,
|
||||||
EventType,
|
EventType,
|
||||||
MatrixEventEvent,
|
MatrixEventEvent,
|
||||||
RoomStateEvent,
|
|
||||||
RoomState,
|
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} from "matrix-js-sdk/src/matrix";
|
||||||
import { ClientWidgetApi, WidgetApiFromWidgetAction } from "matrix-widget-api";
|
import { ClientWidgetApi, WidgetApiFromWidgetAction } from "matrix-widget-api";
|
||||||
import { waitFor } from "jest-matrix-react";
|
import { waitFor } from "jest-matrix-react";
|
||||||
import { Optional } from "matrix-events-sdk";
|
|
||||||
|
|
||||||
import { stubClient, mkRoom, mkEvent } from "../../../test-utils";
|
import { stubClient, mkRoom, mkEvent } from "../../../test-utils";
|
||||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||||
import { StopGapWidget } from "../../../../src/stores/widgets/StopGapWidget";
|
import { StopGapWidget } from "../../../../src/stores/widgets/StopGapWidget";
|
||||||
import ActiveWidgetStore from "../../../../src/stores/ActiveWidgetStore";
|
import ActiveWidgetStore from "../../../../src/stores/ActiveWidgetStore";
|
||||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||||
import { SdkContextClass } from "../../../../src/contexts/SDKContext";
|
|
||||||
import { UPDATE_EVENT } from "../../../../src/stores/AsyncStore";
|
|
||||||
|
|
||||||
jest.mock("matrix-widget-api/lib/ClientWidgetApi");
|
jest.mock("matrix-widget-api/lib/ClientWidgetApi");
|
||||||
|
|
||||||
@@ -58,7 +53,6 @@ describe("StopGapWidget", () => {
|
|||||||
// Start messaging without an iframe, since ClientWidgetApi is mocked
|
// Start messaging without an iframe, since ClientWidgetApi is mocked
|
||||||
widget.startMessaging(null as unknown as HTMLIFrameElement);
|
widget.startMessaging(null as unknown as HTMLIFrameElement);
|
||||||
messaging = mocked(last(mocked(ClientWidgetApi).mock.instances)!);
|
messaging = mocked(last(mocked(ClientWidgetApi).mock.instances)!);
|
||||||
messaging.feedStateUpdate.mockResolvedValue();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -90,20 +84,6 @@ describe("StopGapWidget", () => {
|
|||||||
expect(messaging.feedToDevice).toHaveBeenCalledWith(event.getEffectiveEvent(), false);
|
expect(messaging.feedToDevice).toHaveBeenCalledWith(event.getEffectiveEvent(), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("feeds incoming state updates to the widget", () => {
|
|
||||||
const event = mkEvent({
|
|
||||||
event: true,
|
|
||||||
type: "org.example.foo",
|
|
||||||
skey: "",
|
|
||||||
user: "@alice:example.org",
|
|
||||||
content: { hello: "world" },
|
|
||||||
room: "!1:example.org",
|
|
||||||
});
|
|
||||||
|
|
||||||
client.emit(RoomStateEvent.Events, event, {} as unknown as RoomState, null);
|
|
||||||
expect(messaging.feedStateUpdate).toHaveBeenCalledWith(event.getEffectiveEvent());
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("feed event", () => {
|
describe("feed event", () => {
|
||||||
let event1: MatrixEvent;
|
let event1: MatrixEvent;
|
||||||
let event2: MatrixEvent;
|
let event2: MatrixEvent;
|
||||||
@@ -138,24 +118,24 @@ describe("StopGapWidget", () => {
|
|||||||
|
|
||||||
it("feeds incoming event to the widget", async () => {
|
it("feeds incoming event to the widget", async () => {
|
||||||
client.emit(ClientEvent.Event, event1);
|
client.emit(ClientEvent.Event, event1);
|
||||||
expect(messaging.feedEvent).toHaveBeenCalledWith(event1.getEffectiveEvent());
|
expect(messaging.feedEvent).toHaveBeenCalledWith(event1.getEffectiveEvent(), "!1:example.org");
|
||||||
|
|
||||||
client.emit(ClientEvent.Event, event2);
|
client.emit(ClientEvent.Event, event2);
|
||||||
expect(messaging.feedEvent).toHaveBeenCalledTimes(2);
|
expect(messaging.feedEvent).toHaveBeenCalledTimes(2);
|
||||||
expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2.getEffectiveEvent());
|
expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2.getEffectiveEvent(), "!1:example.org");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not feed incoming event to the widget if seen already", async () => {
|
it("should not feed incoming event to the widget if seen already", async () => {
|
||||||
client.emit(ClientEvent.Event, event1);
|
client.emit(ClientEvent.Event, event1);
|
||||||
expect(messaging.feedEvent).toHaveBeenCalledWith(event1.getEffectiveEvent());
|
expect(messaging.feedEvent).toHaveBeenCalledWith(event1.getEffectiveEvent(), "!1:example.org");
|
||||||
|
|
||||||
client.emit(ClientEvent.Event, event2);
|
client.emit(ClientEvent.Event, event2);
|
||||||
expect(messaging.feedEvent).toHaveBeenCalledTimes(2);
|
expect(messaging.feedEvent).toHaveBeenCalledTimes(2);
|
||||||
expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2.getEffectiveEvent());
|
expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2.getEffectiveEvent(), "!1:example.org");
|
||||||
|
|
||||||
client.emit(ClientEvent.Event, event1);
|
client.emit(ClientEvent.Event, event1);
|
||||||
expect(messaging.feedEvent).toHaveBeenCalledTimes(2);
|
expect(messaging.feedEvent).toHaveBeenCalledTimes(2);
|
||||||
expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2.getEffectiveEvent());
|
expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2.getEffectiveEvent(), "!1:example.org");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("feeds decrypted events asynchronously", async () => {
|
it("feeds decrypted events asynchronously", async () => {
|
||||||
@@ -185,7 +165,7 @@ describe("StopGapWidget", () => {
|
|||||||
decryptingSpy2.mockReturnValue(false);
|
decryptingSpy2.mockReturnValue(false);
|
||||||
client.emit(MatrixEventEvent.Decrypted, event2Encrypted);
|
client.emit(MatrixEventEvent.Decrypted, event2Encrypted);
|
||||||
expect(messaging.feedEvent).toHaveBeenCalledTimes(1);
|
expect(messaging.feedEvent).toHaveBeenCalledTimes(1);
|
||||||
expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2Encrypted.getEffectiveEvent());
|
expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2Encrypted.getEffectiveEvent(), "!1:example.org");
|
||||||
// …then event 1
|
// …then event 1
|
||||||
event1Encrypted.event.type = event1.getType();
|
event1Encrypted.event.type = event1.getType();
|
||||||
event1Encrypted.event.content = event1.getContent();
|
event1Encrypted.event.content = event1.getContent();
|
||||||
@@ -195,7 +175,7 @@ describe("StopGapWidget", () => {
|
|||||||
// doesn't have to be blocked on the decryption of event 1 (or
|
// doesn't have to be blocked on the decryption of event 1 (or
|
||||||
// worse, dropped)
|
// worse, dropped)
|
||||||
expect(messaging.feedEvent).toHaveBeenCalledTimes(2);
|
expect(messaging.feedEvent).toHaveBeenCalledTimes(2);
|
||||||
expect(messaging.feedEvent).toHaveBeenLastCalledWith(event1Encrypted.getEffectiveEvent());
|
expect(messaging.feedEvent).toHaveBeenLastCalledWith(event1Encrypted.getEffectiveEvent(), "!1:example.org");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not feed incoming event if not in timeline", () => {
|
it("should not feed incoming event if not in timeline", () => {
|
||||||
@@ -211,7 +191,7 @@ describe("StopGapWidget", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
client.emit(ClientEvent.Event, event);
|
client.emit(ClientEvent.Event, event);
|
||||||
expect(messaging.feedEvent).toHaveBeenCalledWith(event.getEffectiveEvent());
|
expect(messaging.feedEvent).toHaveBeenCalledWith(event.getEffectiveEvent(), "!1:example.org");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("feeds incoming event that is not in timeline but relates to unknown parent to the widget", async () => {
|
it("feeds incoming event that is not in timeline but relates to unknown parent to the widget", async () => {
|
||||||
@@ -231,19 +211,18 @@ describe("StopGapWidget", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
client.emit(ClientEvent.Event, event1);
|
client.emit(ClientEvent.Event, event1);
|
||||||
expect(messaging.feedEvent).toHaveBeenCalledWith(event1.getEffectiveEvent());
|
expect(messaging.feedEvent).toHaveBeenCalledWith(event1.getEffectiveEvent(), "!1:example.org");
|
||||||
|
|
||||||
client.emit(ClientEvent.Event, event);
|
client.emit(ClientEvent.Event, event);
|
||||||
expect(messaging.feedEvent).toHaveBeenCalledTimes(2);
|
expect(messaging.feedEvent).toHaveBeenCalledTimes(2);
|
||||||
expect(messaging.feedEvent).toHaveBeenLastCalledWith(event.getEffectiveEvent());
|
expect(messaging.feedEvent).toHaveBeenLastCalledWith(event.getEffectiveEvent(), "!1:example.org");
|
||||||
|
|
||||||
client.emit(ClientEvent.Event, event1);
|
client.emit(ClientEvent.Event, event1);
|
||||||
expect(messaging.feedEvent).toHaveBeenCalledTimes(2);
|
expect(messaging.feedEvent).toHaveBeenCalledTimes(2);
|
||||||
expect(messaging.feedEvent).toHaveBeenLastCalledWith(event.getEffectiveEvent());
|
expect(messaging.feedEvent).toHaveBeenLastCalledWith(event.getEffectiveEvent(), "!1:example.org");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("StopGapWidget with stickyPromise", () => {
|
describe("StopGapWidget with stickyPromise", () => {
|
||||||
let client: MockedObject<MatrixClient>;
|
let client: MockedObject<MatrixClient>;
|
||||||
let widget: StopGapWidget;
|
let widget: StopGapWidget;
|
||||||
@@ -309,49 +288,3 @@ describe("StopGapWidget with stickyPromise", () => {
|
|||||||
waitFor(() => expect(setPersistenceSpy).toHaveBeenCalled(), { interval: 5 });
|
waitFor(() => expect(setPersistenceSpy).toHaveBeenCalled(), { interval: 5 });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("StopGapWidget as an account widget", () => {
|
|
||||||
let widget: StopGapWidget;
|
|
||||||
let messaging: MockedObject<ClientWidgetApi>;
|
|
||||||
let getRoomId: MockedFunction<() => Optional<string>>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
stubClient();
|
|
||||||
// I give up, getting the return type of spyOn right is hopeless
|
|
||||||
getRoomId = jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId") as unknown as MockedFunction<
|
|
||||||
() => Optional<string>
|
|
||||||
>;
|
|
||||||
getRoomId.mockReturnValue("!1:example.org");
|
|
||||||
|
|
||||||
widget = new StopGapWidget({
|
|
||||||
app: {
|
|
||||||
id: "test",
|
|
||||||
creatorUserId: "@alice:example.org",
|
|
||||||
type: "example",
|
|
||||||
url: "https://example.org?user-id=$matrix_user_id&device-id=$org.matrix.msc3819.matrix_device_id&base-url=$org.matrix.msc4039.matrix_base_url&theme=$org.matrix.msc2873.client_theme",
|
|
||||||
roomId: "!1:example.org",
|
|
||||||
},
|
|
||||||
userId: "@alice:example.org",
|
|
||||||
creatorUserId: "@alice:example.org",
|
|
||||||
waitForIframeLoad: true,
|
|
||||||
userWidget: false,
|
|
||||||
});
|
|
||||||
// Start messaging without an iframe, since ClientWidgetApi is mocked
|
|
||||||
widget.startMessaging(null as unknown as HTMLIFrameElement);
|
|
||||||
messaging = mocked(last(mocked(ClientWidgetApi).mock.instances)!);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
widget.stopMessaging();
|
|
||||||
getRoomId.mockRestore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates viewed room", () => {
|
|
||||||
expect(messaging.setViewedRoomId).toHaveBeenCalledTimes(1);
|
|
||||||
expect(messaging.setViewedRoomId).toHaveBeenLastCalledWith("!1:example.org");
|
|
||||||
getRoomId.mockReturnValue("!2:example.org");
|
|
||||||
SdkContextClass.instance.roomViewStore.emit(UPDATE_EVENT);
|
|
||||||
expect(messaging.setViewedRoomId).toHaveBeenCalledTimes(2);
|
|
||||||
expect(messaging.setViewedRoomId).toHaveBeenLastCalledWith("!2:example.org");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import {
|
|||||||
MatrixEvent,
|
MatrixEvent,
|
||||||
MsgType,
|
MsgType,
|
||||||
RelationType,
|
RelationType,
|
||||||
Room,
|
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} from "matrix-js-sdk/src/matrix";
|
||||||
import {
|
import {
|
||||||
Widget,
|
Widget,
|
||||||
@@ -39,7 +38,7 @@ import {
|
|||||||
import { SdkContextClass } from "../../../../src/contexts/SDKContext";
|
import { SdkContextClass } from "../../../../src/contexts/SDKContext";
|
||||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||||
import { StopGapWidgetDriver } from "../../../../src/stores/widgets/StopGapWidgetDriver";
|
import { StopGapWidgetDriver } from "../../../../src/stores/widgets/StopGapWidgetDriver";
|
||||||
import { mkEvent, stubClient } from "../../../test-utils";
|
import { stubClient } from "../../../test-utils";
|
||||||
import { ModuleRunner } from "../../../../src/modules/ModuleRunner";
|
import { ModuleRunner } from "../../../../src/modules/ModuleRunner";
|
||||||
import dis from "../../../../src/dispatcher/dispatcher";
|
import dis from "../../../../src/dispatcher/dispatcher";
|
||||||
import Modal from "../../../../src/Modal";
|
import Modal from "../../../../src/Modal";
|
||||||
@@ -570,7 +569,7 @@ describe("StopGapWidgetDriver", () => {
|
|||||||
|
|
||||||
it("passes the flag through to getVisibleRooms", () => {
|
it("passes the flag through to getVisibleRooms", () => {
|
||||||
const driver = mkDefaultDriver();
|
const driver = mkDefaultDriver();
|
||||||
driver.getKnownRooms();
|
driver.readRoomEvents(EventType.CallAnswer, "", 0, ["*"]);
|
||||||
expect(client.getVisibleRooms).toHaveBeenCalledWith(false);
|
expect(client.getVisibleRooms).toHaveBeenCalledWith(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -585,7 +584,7 @@ describe("StopGapWidgetDriver", () => {
|
|||||||
|
|
||||||
it("passes the flag through to getVisibleRooms", () => {
|
it("passes the flag through to getVisibleRooms", () => {
|
||||||
const driver = mkDefaultDriver();
|
const driver = mkDefaultDriver();
|
||||||
driver.getKnownRooms();
|
driver.readRoomEvents(EventType.CallAnswer, "", 0, ["*"]);
|
||||||
expect(client.getVisibleRooms).toHaveBeenCalledWith(true);
|
expect(client.getVisibleRooms).toHaveBeenCalledWith(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -693,107 +692,4 @@ describe("StopGapWidgetDriver", () => {
|
|||||||
await expect(file.text()).resolves.toEqual("test contents");
|
await expect(file.text()).resolves.toEqual("test contents");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("readRoomTimeline", () => {
|
|
||||||
const event1 = mkEvent({
|
|
||||||
event: true,
|
|
||||||
id: "$event-id1",
|
|
||||||
type: "org.example.foo",
|
|
||||||
user: "@alice:example.org",
|
|
||||||
content: { hello: "world" },
|
|
||||||
room: "!1:example.org",
|
|
||||||
});
|
|
||||||
const event2 = mkEvent({
|
|
||||||
event: true,
|
|
||||||
id: "$event-id2",
|
|
||||||
type: "org.example.foo",
|
|
||||||
user: "@alice:example.org",
|
|
||||||
content: { hello: "world" },
|
|
||||||
room: "!1:example.org",
|
|
||||||
});
|
|
||||||
let driver: WidgetDriver;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
driver = mkDefaultDriver();
|
|
||||||
client.getRoom.mockReturnValue({
|
|
||||||
getLiveTimeline: () => ({ getEvents: () => [event1, event2] }),
|
|
||||||
} as unknown as Room);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("reads all events", async () => {
|
|
||||||
expect(
|
|
||||||
await driver.readRoomTimeline("!1:example.org", "org.example.foo", undefined, undefined, 10, undefined),
|
|
||||||
).toEqual([event2, event1].map((e) => e.getEffectiveEvent()));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("reads up to a limit", async () => {
|
|
||||||
expect(
|
|
||||||
await driver.readRoomTimeline("!1:example.org", "org.example.foo", undefined, undefined, 1, undefined),
|
|
||||||
).toEqual([event2.getEffectiveEvent()]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("reads up to a specific event", async () => {
|
|
||||||
expect(
|
|
||||||
await driver.readRoomTimeline(
|
|
||||||
"!1:example.org",
|
|
||||||
"org.example.foo",
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
10,
|
|
||||||
event1.getId(),
|
|
||||||
),
|
|
||||||
).toEqual([event2.getEffectiveEvent()]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("readRoomState", () => {
|
|
||||||
const event1 = mkEvent({
|
|
||||||
event: true,
|
|
||||||
id: "$event-id1",
|
|
||||||
type: "org.example.foo",
|
|
||||||
user: "@alice:example.org",
|
|
||||||
content: { hello: "world" },
|
|
||||||
skey: "1",
|
|
||||||
room: "!1:example.org",
|
|
||||||
});
|
|
||||||
const event2 = mkEvent({
|
|
||||||
event: true,
|
|
||||||
id: "$event-id2",
|
|
||||||
type: "org.example.foo",
|
|
||||||
user: "@alice:example.org",
|
|
||||||
content: { hello: "world" },
|
|
||||||
skey: "2",
|
|
||||||
room: "!1:example.org",
|
|
||||||
});
|
|
||||||
let driver: WidgetDriver;
|
|
||||||
let getStateEvents: jest.Mock;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
driver = mkDefaultDriver();
|
|
||||||
getStateEvents = jest.fn();
|
|
||||||
client.getRoom.mockReturnValue({
|
|
||||||
getLiveTimeline: () => ({ getState: () => ({ getStateEvents }) }),
|
|
||||||
} as unknown as Room);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("reads a specific state key", async () => {
|
|
||||||
getStateEvents.mockImplementation((eventType, stateKey) => {
|
|
||||||
if (eventType === "org.example.foo" && stateKey === "1") return event1;
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
expect(await driver.readRoomState("!1:example.org", "org.example.foo", "1")).toEqual([
|
|
||||||
event1.getEffectiveEvent(),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("reads all state keys", async () => {
|
|
||||||
getStateEvents.mockImplementation((eventType, stateKey) => {
|
|
||||||
if (eventType === "org.example.foo" && stateKey === undefined) return [event1, event2];
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
expect(await driver.readRoomState("!1:example.org", "org.example.foo", undefined)).toEqual(
|
|
||||||
[event1, event2].map((e) => e.getEffectiveEvent()),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8693,10 +8693,10 @@ matrix-web-i18n@^3.2.1:
|
|||||||
minimist "^1.2.8"
|
minimist "^1.2.8"
|
||||||
walk "^2.3.15"
|
walk "^2.3.15"
|
||||||
|
|
||||||
matrix-widget-api@^1.10.0:
|
matrix-widget-api@1.11.0, matrix-widget-api@^1.10.0:
|
||||||
version "1.12.0"
|
version "1.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.12.0.tgz#b3d22bab1670051c8eeee66bb96d08b33148bc99"
|
resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.11.0.tgz#2f548b11a7c0df789d5d4fdb5cc9ef7af8aef3da"
|
||||||
integrity sha512-6JRd9fJGGvuBRhcTg9wX+Skn/Q1wox3jdp5yYQKJ6pPw4urW9bkTR90APBKVDB1vorJKT44jml+lCzkDMRBjww==
|
integrity sha512-ED/9hrJqDWVLeED0g1uJnYRhINh3ZTquwurdM+Hc8wLVJIQ8G/r7A7z74NC+8bBIHQ1Jo7i1Uq5CoJp/TzFYrA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/events" "^3.0.0"
|
"@types/events" "^3.0.0"
|
||||||
events "^3.2.0"
|
events "^3.2.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user