remove unneeded imports

This commit is contained in:
Gigiaj 2025-04-14 09:49:53 -05:00
parent c413396562
commit e2c97f81a7

View file

@ -1,491 +1,493 @@
import { import {
type Capability, type Capability,
EventDirection, EventDirection,
type IOpenIDCredentials, type ISendDelayedEventDetails,
type IOpenIDUpdate, type ISendEventDetails,
type ISendDelayedEventDetails, type IReadEventRelationsResult,
type ISendEventDetails, type IRoomEvent,
type ITurnServer, MatrixCapabilities,
type IReadEventRelationsResult, type Widget,
type IRoomEvent, WidgetDriver,
MatrixCapabilities, WidgetEventCapability,
OpenIDRequestState, WidgetKind,
type SimpleObservable, type IWidgetApiErrorResponseDataDetails,
type Widget, type ISearchUserDirectoryResult,
WidgetDriver, type IGetMediaConfigResult,
WidgetEventCapability, type UpdateDelayedEventAction,
WidgetKind, } from 'matrix-widget-api';
type IWidgetApiErrorResponseDataDetails,
type ISearchUserDirectoryResult,
type IGetMediaConfigResult,
type UpdateDelayedEventAction,
} from "matrix-widget-api";
import { import {
ClientEvent, EventType,
type ITurnServer as IClientTurnServer, type IContent,
EventType, MatrixError,
type IContent, type MatrixEvent,
MatrixError, Direction,
type MatrixEvent, type SendDelayedEventResponse,
Direction, type StateEvents,
THREAD_RELATION_TYPE, type TimelineEvents,
type SendDelayedEventResponse, MatrixClient,
type StateEvents, } from 'matrix-js-sdk';
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
export class SmallWidgetDriver extends WidgetDriver { export class SmallWidgetDriver extends WidgetDriver {
private allowedCapabilities: Set<Capability>; private allowedCapabilities: Set<Capability>;
private readonly mxClient: MatrixClient; // Store the client instance
public constructor( private readonly mxClient: MatrixClient; // Store the client instance
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
this.allowedCapabilities = new Set([ public constructor(
...allowedCapabilities, mx: MatrixClient,
MatrixCapabilities.Screenshots, allowedCapabilities: Capability[],
// Add other base capabilities as needed, e.g., ElementWidgetCapabilities.RequiresClient 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.allowedCapabilities = new Set([
// This is a trusted Element Call widget that we control (adjust if not Element Call) ...allowedCapabilities,
this.allowedCapabilities.add(MatrixCapabilities.AlwaysOnScreen); MatrixCapabilities.Screenshots,
this.allowedCapabilities.add(MatrixCapabilities.MSC3846TurnServers); // Add other base capabilities as needed, e.g., ElementWidgetCapabilities.RequiresClient
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 = [ // --- Capabilities specific to Element Call (or similar trusted widgets) ---
"io.element.call.encryption_keys", // This is a trusted Element Call widget that we control (adjust if not Element Call)
"org.matrix.rageshake_request", this.allowedCapabilities.add(MatrixCapabilities.AlwaysOnScreen);
EventType.Reaction, this.allowedCapabilities.add(MatrixCapabilities.MSC3846TurnServers);
EventType.RoomRedaction, this.allowedCapabilities.add(MatrixCapabilities.MSC4157SendDelayedEvent);
"io.element.call.reaction", this.allowedCapabilities.add(MatrixCapabilities.MSC4157UpdateDelayedEvent);
]; // Capability to access the room timeline (MSC2762)
for (const eventType of sendRecvRoomEvents) { this.allowedCapabilities.add(`org.matrix.msc2762.timeline:${inRoomId}`);
this.allowedCapabilities.add(WidgetEventCapability.forRoomEvent(EventDirection.Send, eventType).raw); // Capability to read room state (MSC2762)
this.allowedCapabilities.add(WidgetEventCapability.forRoomEvent(EventDirection.Receive, eventType).raw); this.allowedCapabilities.add(`org.matrix.msc2762.state:${inRoomId}`);
} this.allowedCapabilities.add(
WidgetEventCapability.forStateEvent(EventDirection.Receive, EventType.RoomMember).raw
const sendRecvToDevice = [ );
EventType.CallInvite, this.allowedCapabilities.add(
EventType.CallCandidates, WidgetEventCapability.forStateEvent(EventDirection.Receive, 'org.matrix.msc3401.call').raw
EventType.CallAnswer, );
EventType.CallHangup, this.allowedCapabilities.add(
EventType.CallReject, WidgetEventCapability.forStateEvent(EventDirection.Receive, EventType.RoomEncryption).raw
EventType.CallSelectAnswer, );
EventType.CallNegotiate, const clientUserId = this.mxClient.getSafeUserId();
EventType.CallSDPStreamMetadataChanged, // For the legacy membership type
EventType.CallSDPStreamMetadataChangedPrefix, this.allowedCapabilities.add(
EventType.CallReplaces, WidgetEventCapability.forStateEvent(
EventType.CallEncryptionKeysPrefix, EventDirection.Send,
]; 'org.matrix.msc3401.call.member',
for (const eventType of sendRecvToDevice) { clientUserId
this.allowedCapabilities.add( ).raw
WidgetEventCapability.forToDeviceEvent(EventDirection.Send, eventType).raw, );
); const clientDeviceId = this.mxClient.getDeviceId();
this.allowedCapabilities.add( if (clientDeviceId !== null) {
WidgetEventCapability.forToDeviceEvent(EventDirection.Receive, eventType).raw, // 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<Capability>): Promise<Set<Capability>> { public async validateCapabilities(requested: Set<Capability>): Promise<Set<Capability>> {
// Stubbed under the assumption voice calls will be valid thru element-call // Stubbed under the assumption voice calls will be valid thru element-call
return requested; return requested;
}
public async sendEvent<K extends keyof StateEvents>(
eventType: K,
content: StateEvents[K],
stateKey: string | null,
targetRoomId: string | null
): Promise<ISendEventDetails>;
public async sendEvent<K extends keyof TimelineEvents>(
eventType: K,
content: TimelineEvents[K],
stateKey: null,
targetRoomId: string | null
): Promise<ISendEventDetails>;
public async sendEvent(
eventType: string,
content: IContent,
stateKey: string | null = null,
targetRoomId: string | null = null
): Promise<ISendEventDetails> {
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<K extends keyof StateEvents>( return { roomId, eventId: r.event_id };
eventType: K, }
content: StateEvents[K],
stateKey: string | null,
targetRoomId: string | null,
): Promise<ISendEventDetails>;
public async sendEvent<K extends keyof TimelineEvents>(
eventType: K,
content: TimelineEvents[K],
stateKey: null,
targetRoomId: string | null,
): Promise<ISendEventDetails>;
public async sendEvent(
eventType: string,
content: IContent,
stateKey: string | null = null,
targetRoomId: string | null = null,
): Promise<ISendEventDetails> {
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"); /**
* @experimental Part of MSC4140 & MSC4157
* @see {@link WidgetDriver#sendDelayedEvent}
*/
public async sendDelayedEvent<K extends keyof StateEvents>(
delay: number | null,
parentDelayId: string | null,
eventType: K,
content: StateEvents[K],
stateKey: string | null,
targetRoomId: string | null
): Promise<ISendDelayedEventDetails>;
/**
* @experimental Part of MSC4140 & MSC4157
*/
public async sendDelayedEvent<K extends keyof TimelineEvents>(
delay: number | null,
parentDelayId: string | null,
eventType: K,
content: TimelineEvents[K],
stateKey: null,
targetRoomId: string | null
): Promise<ISendDelayedEventDetails>;
public async sendDelayedEvent(
delay: number | null,
parentDelayId: string | null,
eventType: string,
content: IContent,
stateKey: string | null = null,
targetRoomId: string | null = null
): Promise<ISendDelayedEventDetails> {
const client = this.mxClient;
const roomId = targetRoomId || this.inRoomId;
let r: { event_id: string } | null; if (!client || !roomId) throw new Error('Not in a room or not attached to a client');
if (stateKey !== null) {
// state event let delayOpts;
r = await client.sendStateEvent( if (delay !== null) {
roomId, delayOpts = {
eventType as keyof StateEvents, delay,
content as StateEvents[keyof StateEvents], ...(parentDelayId !== null && { parent_delay_id: parentDelayId }),
stateKey, };
); } else if (parentDelayId !== null) {
} else if (eventType === EventType.RoomRedaction) { delayOpts = {
// special case: extract the `redacts` property and call redact parent_delay_id: parentDelayId,
r = await client.redactEvent(roomId, content["redacts"]); };
} else { } else {
// message event throw new Error('Must provide at least one of delay or parentDelayId');
r = await client.sendEvent( }
roomId,
eventType as keyof TimelineEvents, let r: SendDelayedEventResponse | null;
content as TimelineEvents[keyof TimelineEvents], 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<void> {
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<void> {
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<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 = 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);
* @experimental Part of MSC4140 & MSC4157 }
* @see {@link WidgetDriver#sendDelayedEvent}
*/
public async sendDelayedEvent<K extends keyof StateEvents>(
delay: number | null,
parentDelayId: string | null,
eventType: K,
content: StateEvents[K],
stateKey: string | null,
targetRoomId: string | null,
): Promise<ISendDelayedEventDetails>;
/**
* @experimental Part of MSC4140 & MSC4157
*/
public async sendDelayedEvent<K extends keyof TimelineEvents>(
delay: number | null,
parentDelayId: string | null,
eventType: K,
content: TimelineEvents[K],
stateKey: null,
targetRoomId: string | null,
): Promise<ISendDelayedEventDetails>;
public async sendDelayedEvent(
delay: number | null,
parentDelayId: string | null,
eventType: string,
content: IContent,
stateKey: string | null = null,
targetRoomId: string | null = null,
): Promise<ISendDelayedEventDetails> {
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"); /**
* 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<IRoomEvent[]>} Resolves to the events representing the
* current values of the room state entries.
*/
public async readRoomState(
roomId: string,
eventType: string,
stateKey: string | undefined
): Promise<IRoomEvent[]> {
const room = this.mxClient.getRoom(roomId);
if (room === null) return [];
const state = room.getLiveTimeline().getState(Direction.Forward);
if (state === undefined) return [];
let delayOpts; if (stateKey === undefined)
if (delay !== null) { return state.getStateEvents(eventType).map((e) => e.getEffectiveEvent() as IRoomEvent);
delayOpts = { const event = state.getStateEvents(eventType, stateKey);
delay, return event === null ? [] : [event.getEffectiveEvent() as IRoomEvent];
...(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<void> {
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<void> {
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<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 = 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<IRoomEvent[]>} Resolves to the events representing the
* current values of the room state entries.
*/
public async readRoomState(roomId: string, eventType: string, stateKey: string | undefined): Promise<IRoomEvent[]> {
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<void> { public async navigate(uri: string): Promise<void> {
navigateToPermalink(uri); navigateToPermalink(uri);
} }
*/ */
public async readEventRelations( public async readEventRelations(
eventId: string, eventId: string,
roomId?: string, roomId?: string,
relationType?: string, relationType?: string,
eventType?: string, eventType?: string,
from?: string, from?: string,
to?: string, to?: string,
limit?: number, limit?: number,
direction?: "f" | "b", direction?: 'f' | 'b'
): Promise<IReadEventRelationsResult> { ): Promise<IReadEventRelationsResult> {
const client = this.mxClient; const client = this.mxClient;
const dir = direction as Direction; const dir = direction as Direction;
roomId = roomId ?? this.inRoomId ?? undefined; roomId = roomId ?? this.inRoomId ?? undefined;
if (typeof roomId !== "string") { if (typeof roomId !== 'string') {
throw new Error("Error while reading the current room"); 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,
};
} }
public async searchUserDirectory(searchTerm: string, limit?: number): Promise<ISearchUserDirectoryResult> { const { events, nextBatch, prevBatch } = await client.relations(
const client = this.mxClient; 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 { public async searchUserDirectory(
limited, searchTerm: string,
results: results.map((r) => ({ limit?: number
userId: r.user_id, ): Promise<ISearchUserDirectoryResult> {
displayName: r.display_name, const client = this.mxClient;
avatarUrl: r.avatar_url,
})),
};
}
public async getMediaConfig(): Promise<IGetMediaConfigResult> { const { limited, results } = await client.searchUserDirectory({ term: searchTerm, limit });
const client = this.mxClient;
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 }> { public async getMediaConfig(): Promise<IGetMediaConfigResult> {
const client = this.mxClient; 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;
/** const uploadResult = await client.uploadContent(file);
* Download a file from the media repository on the homeserver.
* return { contentUri: uploadResult.content_uri };
* @param contentUri - the MXC URI of the file to download }
* @returns an object with: file - response contents as Blob
*/ /**
/* * 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 }> { public async downloadFile(contentUri: string): Promise<{ file: XMLHttpRequestBodyInit }> {
const client = this.mxClient; const client = this.mxClient;
const media = mediaFromMxc(contentUri, client); 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 * Gets the IDs of all joined or invited rooms currently known to the
* client. * client.
* @returns The room IDs. * @returns The room IDs.
*/ */
public getKnownRooms(): string[] { public getKnownRooms(): string[] {
return this.mxClient return this.mxClient.getVisibleRooms().map((r) => r.roomId);
.getVisibleRooms() }
.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.
* @param error The error to handle. * @param error The error to handle.
* @returns The error expressed as a JSON payload, * @returns The error expressed as a JSON payload,
* or undefined if it is not a {@link MatrixError}. * or undefined if it is not a {@link MatrixError}.
*/ */
public processError(error: unknown): IWidgetApiErrorResponseDataDetails | undefined { public processError(error: unknown): IWidgetApiErrorResponseDataDetails | undefined {
return error instanceof MatrixError ? { matrix_api_error: error.asWidgetApiErrorData() } : undefined; return error instanceof MatrixError
} ? { matrix_api_error: error.asWidgetApiErrorData() }
} : undefined;
}
}