diff --git a/src/app/features/room/SmallWidgetDriver.ts b/src/app/features/room/SmallWidgetDriver.ts index 0833111e..5c13e405 100644 --- a/src/app/features/room/SmallWidgetDriver.ts +++ b/src/app/features/room/SmallWidgetDriver.ts @@ -1,491 +1,493 @@ import { - type Capability, - EventDirection, - type IOpenIDCredentials, - type IOpenIDUpdate, - type ISendDelayedEventDetails, - type ISendEventDetails, - type ITurnServer, - type IReadEventRelationsResult, - type IRoomEvent, - MatrixCapabilities, - OpenIDRequestState, - type SimpleObservable, - type Widget, - WidgetDriver, - WidgetEventCapability, - WidgetKind, - type IWidgetApiErrorResponseDataDetails, - type ISearchUserDirectoryResult, - type IGetMediaConfigResult, - type UpdateDelayedEventAction, -} from "matrix-widget-api"; + type Capability, + EventDirection, + type ISendDelayedEventDetails, + type ISendEventDetails, + type IReadEventRelationsResult, + type IRoomEvent, + MatrixCapabilities, + type Widget, + WidgetDriver, + WidgetEventCapability, + WidgetKind, + type IWidgetApiErrorResponseDataDetails, + type ISearchUserDirectoryResult, + type IGetMediaConfigResult, + type UpdateDelayedEventAction, +} from 'matrix-widget-api'; import { - ClientEvent, - type ITurnServer as IClientTurnServer, - EventType, - type IContent, - MatrixError, - type MatrixEvent, - Direction, - THREAD_RELATION_TYPE, - type SendDelayedEventResponse, - type StateEvents, - type TimelineEvents, - MatrixClient, - Room, // Import Room - Timeline, // Import Timeline -} from "matrix-js-sdk"; // Assuming matrix-js-sdk is correctly imported -import { - type ApprovalOpts, - type CapabilitiesOpts, - WidgetLifecycle, -} from "@matrix-org/react-sdk-module-api/lib/lifecycles/WidgetLifecycle"; // Assuming this path is correct -import { logger } from "matrix-js-sdk/lib/logger"; // Assuming logger is correctly imported - + EventType, + type IContent, + MatrixError, + type MatrixEvent, + Direction, + type SendDelayedEventResponse, + type StateEvents, + type TimelineEvents, + MatrixClient, +} from 'matrix-js-sdk'; export class SmallWidgetDriver extends WidgetDriver { - private allowedCapabilities: Set; - private readonly mxClient: MatrixClient; // Store the client instance + private allowedCapabilities: Set; - public constructor( - mx: MatrixClient, - allowedCapabilities: Capability[], - private forWidget: Widget, - private forWidgetKind: WidgetKind, - virtual: boolean, // Assuming 'virtual' might be needed later, kept for consistency - private inRoomId?: string, - ) { - super(); - this.mxClient = mx; // Store the passed instance + private readonly mxClient: MatrixClient; // Store the client instance - this.allowedCapabilities = new Set([ - ...allowedCapabilities, - MatrixCapabilities.Screenshots, - // Add other base capabilities as needed, e.g., ElementWidgetCapabilities.RequiresClient - ]); + public constructor( + mx: MatrixClient, + allowedCapabilities: Capability[], + private forWidget: Widget, + private forWidgetKind: WidgetKind, + virtual: boolean, // Assuming 'virtual' might be needed later, kept for consistency + private inRoomId?: string + ) { + super(); + this.mxClient = mx; // Store the passed instance - // --- Capabilities specific to Element Call (or similar trusted widgets) --- - // This is a trusted Element Call widget that we control (adjust if not Element Call) - this.allowedCapabilities.add(MatrixCapabilities.AlwaysOnScreen); - this.allowedCapabilities.add(MatrixCapabilities.MSC3846TurnServers); - this.allowedCapabilities.add(MatrixCapabilities.MSC4157SendDelayedEvent); - this.allowedCapabilities.add(MatrixCapabilities.MSC4157UpdateDelayedEvent); - // Capability to access the room timeline (MSC2762) - this.allowedCapabilities.add(`org.matrix.msc2762.timeline:${inRoomId}`); - // Capability to read room state (MSC2762) - this.allowedCapabilities.add(`org.matrix.msc2762.state:${inRoomId}`); - this.allowedCapabilities.add( - WidgetEventCapability.forStateEvent(EventDirection.Receive, EventType.RoomMember).raw, - ); - this.allowedCapabilities.add( - WidgetEventCapability.forStateEvent(EventDirection.Receive, "org.matrix.msc3401.call").raw, - ); - this.allowedCapabilities.add( - WidgetEventCapability.forStateEvent(EventDirection.Receive, EventType.RoomEncryption).raw, - ); - const clientUserId = this.mxClient.getSafeUserId(); - // For the legacy membership type - this.allowedCapabilities.add( - WidgetEventCapability.forStateEvent(EventDirection.Send, "org.matrix.msc3401.call.member", clientUserId) - .raw, - ); - const clientDeviceId = this.mxClient.getDeviceId(); - if (clientDeviceId !== null) { - // For the session membership type compliant with MSC4143 - this.allowedCapabilities.add( - WidgetEventCapability.forStateEvent( - EventDirection.Send, - "org.matrix.msc3401.call.member", - `_${clientUserId}_${clientDeviceId}`, - ).raw, - ); - // Version with no leading underscore, for room versions whose auth rules allow it - this.allowedCapabilities.add( - WidgetEventCapability.forStateEvent( - EventDirection.Send, - "org.matrix.msc3401.call.member", - `${clientUserId}_${clientDeviceId}`, - ).raw, - ); - } - this.allowedCapabilities.add( - WidgetEventCapability.forStateEvent(EventDirection.Receive, "org.matrix.msc3401.call.member").raw, - ); - // for determining auth rules specific to the room version - this.allowedCapabilities.add( - WidgetEventCapability.forStateEvent(EventDirection.Receive, EventType.RoomCreate).raw, - ); + this.allowedCapabilities = new Set([ + ...allowedCapabilities, + MatrixCapabilities.Screenshots, + // Add other base capabilities as needed, e.g., ElementWidgetCapabilities.RequiresClient + ]); - const sendRecvRoomEvents = [ - "io.element.call.encryption_keys", - "org.matrix.rageshake_request", - EventType.Reaction, - EventType.RoomRedaction, - "io.element.call.reaction", - ]; - for (const eventType of sendRecvRoomEvents) { - this.allowedCapabilities.add(WidgetEventCapability.forRoomEvent(EventDirection.Send, eventType).raw); - this.allowedCapabilities.add(WidgetEventCapability.forRoomEvent(EventDirection.Receive, eventType).raw); - } - - const sendRecvToDevice = [ - EventType.CallInvite, - EventType.CallCandidates, - EventType.CallAnswer, - EventType.CallHangup, - EventType.CallReject, - EventType.CallSelectAnswer, - EventType.CallNegotiate, - EventType.CallSDPStreamMetadataChanged, - EventType.CallSDPStreamMetadataChangedPrefix, - EventType.CallReplaces, - EventType.CallEncryptionKeysPrefix, - ]; - for (const eventType of sendRecvToDevice) { - this.allowedCapabilities.add( - WidgetEventCapability.forToDeviceEvent(EventDirection.Send, eventType).raw, - ); - this.allowedCapabilities.add( - WidgetEventCapability.forToDeviceEvent(EventDirection.Receive, eventType).raw, - ); - } + // --- Capabilities specific to Element Call (or similar trusted widgets) --- + // This is a trusted Element Call widget that we control (adjust if not Element Call) + this.allowedCapabilities.add(MatrixCapabilities.AlwaysOnScreen); + this.allowedCapabilities.add(MatrixCapabilities.MSC3846TurnServers); + this.allowedCapabilities.add(MatrixCapabilities.MSC4157SendDelayedEvent); + this.allowedCapabilities.add(MatrixCapabilities.MSC4157UpdateDelayedEvent); + // Capability to access the room timeline (MSC2762) + this.allowedCapabilities.add(`org.matrix.msc2762.timeline:${inRoomId}`); + // Capability to read room state (MSC2762) + this.allowedCapabilities.add(`org.matrix.msc2762.state:${inRoomId}`); + this.allowedCapabilities.add( + WidgetEventCapability.forStateEvent(EventDirection.Receive, EventType.RoomMember).raw + ); + this.allowedCapabilities.add( + WidgetEventCapability.forStateEvent(EventDirection.Receive, 'org.matrix.msc3401.call').raw + ); + this.allowedCapabilities.add( + WidgetEventCapability.forStateEvent(EventDirection.Receive, EventType.RoomEncryption).raw + ); + const clientUserId = this.mxClient.getSafeUserId(); + // For the legacy membership type + this.allowedCapabilities.add( + WidgetEventCapability.forStateEvent( + EventDirection.Send, + 'org.matrix.msc3401.call.member', + clientUserId + ).raw + ); + const clientDeviceId = this.mxClient.getDeviceId(); + if (clientDeviceId !== null) { + // For the session membership type compliant with MSC4143 + this.allowedCapabilities.add( + WidgetEventCapability.forStateEvent( + EventDirection.Send, + 'org.matrix.msc3401.call.member', + `_${clientUserId}_${clientDeviceId}` + ).raw + ); + // Version with no leading underscore, for room versions whose auth rules allow it + this.allowedCapabilities.add( + WidgetEventCapability.forStateEvent( + EventDirection.Send, + 'org.matrix.msc3401.call.member', + `${clientUserId}_${clientDeviceId}` + ).raw + ); + } + this.allowedCapabilities.add( + WidgetEventCapability.forStateEvent(EventDirection.Receive, 'org.matrix.msc3401.call.member') + .raw + ); + // for determining auth rules specific to the room version + this.allowedCapabilities.add( + WidgetEventCapability.forStateEvent(EventDirection.Receive, EventType.RoomCreate).raw + ); + const sendRecvRoomEvents = [ + 'io.element.call.encryption_keys', + 'org.matrix.rageshake_request', + EventType.Reaction, + EventType.RoomRedaction, + 'io.element.call.reaction', + ]; + for (const eventType of sendRecvRoomEvents) { + this.allowedCapabilities.add( + WidgetEventCapability.forRoomEvent(EventDirection.Send, eventType).raw + ); + this.allowedCapabilities.add( + WidgetEventCapability.forRoomEvent(EventDirection.Receive, eventType).raw + ); } + const sendRecvToDevice = [ + EventType.CallInvite, + EventType.CallCandidates, + EventType.CallAnswer, + EventType.CallHangup, + EventType.CallReject, + EventType.CallSelectAnswer, + EventType.CallNegotiate, + EventType.CallSDPStreamMetadataChanged, + EventType.CallSDPStreamMetadataChangedPrefix, + EventType.CallReplaces, + EventType.CallEncryptionKeysPrefix, + ]; + for (const eventType of sendRecvToDevice) { + this.allowedCapabilities.add( + WidgetEventCapability.forToDeviceEvent(EventDirection.Send, eventType).raw + ); + this.allowedCapabilities.add( + WidgetEventCapability.forToDeviceEvent(EventDirection.Receive, eventType).raw + ); + } + } - public async validateCapabilities(requested: Set): Promise> { - // Stubbed under the assumption voice calls will be valid thru element-call - return requested; + public async validateCapabilities(requested: Set): Promise> { + // Stubbed under the assumption voice calls will be valid thru element-call + return requested; + } + + public async sendEvent( + eventType: K, + content: StateEvents[K], + stateKey: string | null, + targetRoomId: string | null + ): Promise; + public async sendEvent( + eventType: K, + content: TimelineEvents[K], + stateKey: null, + targetRoomId: string | null + ): Promise; + public async sendEvent( + eventType: string, + content: IContent, + stateKey: string | null = null, + targetRoomId: string | null = null + ): Promise { + const client = this.mxClient; + const roomId = targetRoomId || this.inRoomId; + + if (!client || !roomId) throw new Error('Not in a room or not attached to a client'); + + let r: { event_id: string } | null; + if (stateKey !== null) { + // state event + r = await client.sendStateEvent( + roomId, + eventType as keyof StateEvents, + content as StateEvents[keyof StateEvents], + stateKey + ); + } else if (eventType === EventType.RoomRedaction) { + // special case: extract the `redacts` property and call redact + r = await client.redactEvent(roomId, content['redacts']); + } else { + // message event + r = await client.sendEvent( + roomId, + eventType as keyof TimelineEvents, + content as TimelineEvents[keyof TimelineEvents] + ); } - public async sendEvent( - eventType: K, - content: StateEvents[K], - stateKey: string | null, - targetRoomId: string | null, - ): Promise; - public async sendEvent( - eventType: K, - content: TimelineEvents[K], - stateKey: null, - targetRoomId: string | null, - ): Promise; - public async sendEvent( - eventType: string, - content: IContent, - stateKey: string | null = null, - targetRoomId: string | null = null, - ): Promise { - const client = this.mxClient; - const roomId = targetRoomId || this.inRoomId; + return { roomId, eventId: r.event_id }; + } - if (!client || !roomId) throw new Error("Not in a room or not attached to a client"); + /** + * @experimental Part of MSC4140 & MSC4157 + * @see {@link WidgetDriver#sendDelayedEvent} + */ + public async sendDelayedEvent( + delay: number | null, + parentDelayId: string | null, + eventType: K, + content: StateEvents[K], + stateKey: string | null, + targetRoomId: string | null + ): Promise; + /** + * @experimental Part of MSC4140 & MSC4157 + */ + public async sendDelayedEvent( + delay: number | null, + parentDelayId: string | null, + eventType: K, + content: TimelineEvents[K], + stateKey: null, + targetRoomId: string | null + ): Promise; + public async sendDelayedEvent( + delay: number | null, + parentDelayId: string | null, + eventType: string, + content: IContent, + stateKey: string | null = null, + targetRoomId: string | null = null + ): Promise { + const client = this.mxClient; + const roomId = targetRoomId || this.inRoomId; - let r: { event_id: string } | null; - if (stateKey !== null) { - // state event - r = await client.sendStateEvent( - roomId, - eventType as keyof StateEvents, - content as StateEvents[keyof StateEvents], - stateKey, - ); - } else if (eventType === EventType.RoomRedaction) { - // special case: extract the `redacts` property and call redact - r = await client.redactEvent(roomId, content["redacts"]); - } else { - // message event - r = await client.sendEvent( - roomId, - eventType as keyof TimelineEvents, - content as TimelineEvents[keyof TimelineEvents], - ); + if (!client || !roomId) throw new Error('Not in a room or not attached to a client'); + + let delayOpts; + if (delay !== null) { + delayOpts = { + delay, + ...(parentDelayId !== null && { parent_delay_id: parentDelayId }), + }; + } else if (parentDelayId !== null) { + delayOpts = { + parent_delay_id: parentDelayId, + }; + } else { + throw new Error('Must provide at least one of delay or parentDelayId'); + } + + let r: SendDelayedEventResponse | null; + if (stateKey !== null) { + // state event + r = await client._unstable_sendDelayedStateEvent( + roomId, + delayOpts, + eventType as keyof StateEvents, + content as StateEvents[keyof StateEvents], + stateKey + ); + } else { + // message event + r = await client._unstable_sendDelayedEvent( + roomId, + delayOpts, + null, + eventType as keyof TimelineEvents, + content as TimelineEvents[keyof TimelineEvents] + ); + } + + return { + roomId, + delayId: r.delay_id, + }; + } + + /** + * @experimental Part of MSC4140 & MSC4157 + */ + public async updateDelayedEvent( + delayId: string, + action: UpdateDelayedEventAction + ): Promise { + const client = this.mxClient; + + if (!client) throw new Error('Not in a room or not attached to a client'); + + await client._unstable_updateDelayedEvent(delayId, action); + } + + /** + * Implements {@link WidgetDriver#sendToDevice} + */ + public async sendToDevice( + eventType: string, + encrypted: boolean, + contentMap: { [userId: string]: { [deviceId: string]: object } } + ): Promise { + const client = this.mxClient; + + if (encrypted) { + const crypto = client.getCrypto(); + if (!crypto) throw new Error('E2EE not enabled'); + + // attempt to re-batch these up into a single request + const invertedContentMap: { [content: string]: { userId: string; deviceId: string }[] } = {}; + + for (const userId of Object.keys(contentMap)) { + const userContentMap = contentMap[userId]; + for (const deviceId of Object.keys(userContentMap)) { + const content = userContentMap[deviceId]; + const stringifiedContent = JSON.stringify(content); + invertedContentMap[stringifiedContent] = invertedContentMap[stringifiedContent] || []; + invertedContentMap[stringifiedContent].push({ userId, deviceId }); } + } - return { roomId, eventId: r.event_id }; + await Promise.all( + Object.entries(invertedContentMap).map(async ([stringifiedContent, recipients]) => { + const batch = await crypto.encryptToDeviceMessages( + eventType, + recipients, + JSON.parse(stringifiedContent) + ); + + await client.queueToDevice(batch); + }) + ); + } else { + await client.queueToDevice({ + eventType, + batch: Object.entries(contentMap).flatMap(([userId, userContentMap]) => + Object.entries(userContentMap).map(([deviceId, content]) => ({ + userId, + deviceId, + payload: content, + })) + ), + }); + } + } + + /** + * Reads all events of the given type, and optionally `msgtype` (if applicable/defined), + * the user has access to. The widget API will have already verified that the widget is + * 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} 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 { + limit = limit > 0 ? Math.min(limit, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary + + const room = this.mxClient.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; + + 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); } - /** - * @experimental Part of MSC4140 & MSC4157 - * @see {@link WidgetDriver#sendDelayedEvent} - */ - public async sendDelayedEvent( - delay: number | null, - parentDelayId: string | null, - eventType: K, - content: StateEvents[K], - stateKey: string | null, - targetRoomId: string | null, - ): Promise; - /** - * @experimental Part of MSC4140 & MSC4157 - */ - public async sendDelayedEvent( - delay: number | null, - parentDelayId: string | null, - eventType: K, - content: TimelineEvents[K], - stateKey: null, - targetRoomId: string | null, - ): Promise; - public async sendDelayedEvent( - delay: number | null, - parentDelayId: string | null, - eventType: string, - content: IContent, - stateKey: string | null = null, - targetRoomId: string | null = null, - ): Promise { - const client = this.mxClient; - const roomId = targetRoomId || this.inRoomId; + return results.map((e) => e.getEffectiveEvent() as IRoomEvent); + } - if (!client || !roomId) throw new Error("Not in a room or not attached to a client"); + /** + * Reads the current values of all matching room state entries. + * @param roomId The ID of the room. + * @param eventType The event type of the entries to be read. + * @param stateKey The state key of the entry to be read. If undefined, + * all room state entries with a matching event type should be returned. + * @returns {Promise} Resolves to the events representing the + * current values of the room state entries. + */ + public async readRoomState( + roomId: string, + eventType: string, + stateKey: string | undefined + ): Promise { + const room = this.mxClient.getRoom(roomId); + if (room === null) return []; + const state = room.getLiveTimeline().getState(Direction.Forward); + if (state === undefined) return []; - let delayOpts; - if (delay !== null) { - delayOpts = { - delay, - ...(parentDelayId !== null && { parent_delay_id: parentDelayId }), - }; - } else if (parentDelayId !== null) { - delayOpts = { - parent_delay_id: parentDelayId, - }; - } else { - throw new Error("Must provide at least one of delay or parentDelayId"); - } + 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]; + } - let r: SendDelayedEventResponse | null; - if (stateKey !== null) { - // state event - r = await client._unstable_sendDelayedStateEvent( - roomId, - delayOpts, - eventType as keyof StateEvents, - content as StateEvents[keyof StateEvents], - stateKey, - ); - } else { - // message event - r = await client._unstable_sendDelayedEvent( - roomId, - delayOpts, - null, - eventType as keyof TimelineEvents, - content as TimelineEvents[keyof TimelineEvents], - ); - } - - return { - roomId, - delayId: r.delay_id, - }; - } - - /** - * @experimental Part of MSC4140 & MSC4157 - */ - public async updateDelayedEvent(delayId: string, action: UpdateDelayedEventAction): Promise { - const client = this.mxClient; - - if (!client) throw new Error("Not in a room or not attached to a client"); - - await client._unstable_updateDelayedEvent(delayId, action); - } - - /** - * Implements {@link WidgetDriver#sendToDevice} - */ - public async sendToDevice( - eventType: string, - encrypted: boolean, - contentMap: { [userId: string]: { [deviceId: string]: object } }, - ): Promise { - const client = this.mxClient; - - if (encrypted) { - const crypto = client.getCrypto(); - if (!crypto) throw new Error("E2EE not enabled"); - - // attempt to re-batch these up into a single request - const invertedContentMap: { [content: string]: { userId: string; deviceId: string }[] } = {}; - - for (const userId of Object.keys(contentMap)) { - const userContentMap = contentMap[userId]; - for (const deviceId of Object.keys(userContentMap)) { - const content = userContentMap[deviceId]; - const stringifiedContent = JSON.stringify(content); - invertedContentMap[stringifiedContent] = invertedContentMap[stringifiedContent] || []; - invertedContentMap[stringifiedContent].push({ userId, deviceId }); - } - } - - await Promise.all( - Object.entries(invertedContentMap).map(async ([stringifiedContent, recipients]) => { - const batch = await crypto.encryptToDeviceMessages( - eventType, - recipients, - JSON.parse(stringifiedContent), - ); - - await client.queueToDevice(batch); - }), - ); - } else { - await client.queueToDevice({ - eventType, - batch: Object.entries(contentMap).flatMap(([userId, userContentMap]) => - Object.entries(userContentMap).map(([deviceId, content]) => ({ - userId, - deviceId, - payload: content, - })), - ), - }); - } - } - - /** - * Reads all events of the given type, and optionally `msgtype` (if applicable/defined), - * the user has access to. The widget API will have already verified that the widget is - * 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} 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 { - limit = limit > 0 ? Math.min(limit, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary - - const room = this.mxClient.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; - - 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); - } - - /** - * Reads the current values of all matching room state entries. - * @param roomId The ID of the room. - * @param eventType The event type of the entries to be read. - * @param stateKey The state key of the entry to be read. If undefined, - * all room state entries with a matching event type should be returned. - * @returns {Promise} Resolves to the events representing the - * current values of the room state entries. - */ - public async readRoomState(roomId: string, eventType: string, stateKey: string | undefined): Promise { - const room = this.mxClient.getRoom(roomId); - if (room === null) return []; - const state = room.getLiveTimeline().getState(Direction.Forward); - if (state === undefined) return []; - - 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 navigate(uri: string): Promise { navigateToPermalink(uri); } */ - public async readEventRelations( - eventId: string, - roomId?: string, - relationType?: string, - eventType?: string, - from?: string, - to?: string, - limit?: number, - direction?: "f" | "b", - ): Promise { - const client = this.mxClient; - const dir = direction as Direction; - roomId = roomId ?? this.inRoomId ?? undefined; + public async readEventRelations( + eventId: string, + roomId?: string, + relationType?: string, + eventType?: string, + from?: string, + to?: string, + limit?: number, + direction?: 'f' | 'b' + ): Promise { + const client = this.mxClient; + const dir = direction as Direction; + roomId = roomId ?? this.inRoomId ?? undefined; - if (typeof roomId !== "string") { - throw new Error("Error while reading the current room"); - } - - const { events, nextBatch, prevBatch } = await client.relations( - roomId, - eventId, - relationType ?? null, - eventType ?? null, - { from, to, limit, dir }, - ); - - return { - chunk: events.map((e) => e.getEffectiveEvent() as IRoomEvent), - nextBatch: nextBatch ?? undefined, - prevBatch: prevBatch ?? undefined, - }; + if (typeof roomId !== 'string') { + throw new Error('Error while reading the current room'); } - public async searchUserDirectory(searchTerm: string, limit?: number): Promise { - const client = this.mxClient; + const { events, nextBatch, prevBatch } = await client.relations( + roomId, + eventId, + relationType ?? null, + eventType ?? null, + { from, to, limit, dir } + ); - const { limited, results } = await client.searchUserDirectory({ term: searchTerm, limit }); + return { + chunk: events.map((e) => e.getEffectiveEvent() as IRoomEvent), + nextBatch: nextBatch ?? undefined, + prevBatch: prevBatch ?? undefined, + }; + } - return { - limited, - results: results.map((r) => ({ - userId: r.user_id, - displayName: r.display_name, - avatarUrl: r.avatar_url, - })), - }; - } + public async searchUserDirectory( + searchTerm: string, + limit?: number + ): Promise { + const client = this.mxClient; - public async getMediaConfig(): Promise { - const client = this.mxClient; + const { limited, results } = await client.searchUserDirectory({ term: searchTerm, limit }); - return await client.getMediaConfig(); - } + return { + limited, + results: results.map((r) => ({ + userId: r.user_id, + displayName: r.display_name, + avatarUrl: r.avatar_url, + })), + }; + } - public async uploadFile(file: XMLHttpRequestBodyInit): Promise<{ contentUri: string }> { - const client = this.mxClient; + public async getMediaConfig(): Promise { + const client = this.mxClient; - const uploadResult = await client.uploadContent(file); + return await client.getMediaConfig(); + } - return { contentUri: uploadResult.content_uri }; - } + public async uploadFile(file: XMLHttpRequestBodyInit): Promise<{ contentUri: string }> { + const client = this.mxClient; - /** - * Download a file from the media repository on the homeserver. - * - * @param contentUri - the MXC URI of the file to download - * @returns an object with: file - response contents as Blob - */ - /* + const uploadResult = await client.uploadContent(file); + + return { contentUri: uploadResult.content_uri }; + } + + /** + * Download a file from the media repository on the homeserver. + * + * @param contentUri - the MXC URI of the file to download + * @returns an object with: file - response contents as Blob + */ + /* public async downloadFile(contentUri: string): Promise<{ file: XMLHttpRequestBodyInit }> { const client = this.mxClient; const media = mediaFromMxc(contentUri, client); @@ -495,25 +497,25 @@ export class SmallWidgetDriver extends WidgetDriver { } */ - /** - * Gets the IDs of all joined or invited rooms currently known to the - * client. - * @returns The room IDs. - */ - public getKnownRooms(): string[] { - return this.mxClient - .getVisibleRooms() - .map((r) => r.roomId); - } + /** + * Gets the IDs of all joined or invited rooms currently known to the + * client. + * @returns The room IDs. + */ + public getKnownRooms(): string[] { + return this.mxClient.getVisibleRooms().map((r) => r.roomId); + } - /** - * Expresses a {@link MatrixError} as a JSON payload - * for use by Widget API error responses. - * @param error The error to handle. - * @returns The error expressed as a JSON payload, - * or undefined if it is not a {@link MatrixError}. - */ - public processError(error: unknown): IWidgetApiErrorResponseDataDetails | undefined { - return error instanceof MatrixError ? { matrix_api_error: error.asWidgetApiErrorData() } : undefined; - } -} \ No newline at end of file + /** + * Expresses a {@link MatrixError} as a JSON payload + * for use by Widget API error responses. + * @param error The error to handle. + * @returns The error expressed as a JSON payload, + * or undefined if it is not a {@link MatrixError}. + */ + public processError(error: unknown): IWidgetApiErrorResponseDataDetails | undefined { + return error instanceof MatrixError + ? { matrix_api_error: error.asWidgetApiErrorData() } + : undefined; + } +}