Update StopGapWidgetDriver to support sticky events (#31205)

* Update StopGapWidgetDriver to support sticky events

* Update to-device

* Update tests

* Clean up duplication

* lint

* yarn lock
This commit is contained in:
Will Hunt
2025-12-29 13:34:01 +00:00
committed by GitHub
parent 3f472c8812
commit 004dbcd062
6 changed files with 166 additions and 42 deletions

View File

@@ -39,6 +39,8 @@ import {
type SendDelayedEventResponse,
type StateEvents,
type TimelineEvents,
type SendDelayedEventRequestOpts,
type MatrixClient,
} from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import {
@@ -122,6 +124,7 @@ export class ElementWidgetDriver extends WidgetDriver {
this.allowedCapabilities.add(`org.matrix.msc2762.timeline:${inRoomId}`);
this.allowedCapabilities.add(MatrixCapabilities.MSC4157SendDelayedEvent);
this.allowedCapabilities.add(MatrixCapabilities.MSC4157UpdateDelayedEvent);
this.allowedCapabilities.add(MatrixCapabilities.MSC4354SendStickyEvent);
this.allowedCapabilities.add(
WidgetEventCapability.forStateEvent(EventDirection.Receive, EventType.RoomName).raw,
@@ -288,6 +291,13 @@ export class ElementWidgetDriver extends WidgetDriver {
return allAllowed;
}
private getSendEventTarget(roomId: string | null = null): { client: MatrixClient; roomId: string } {
const client = MatrixClientPeg.safeGet();
roomId = roomId || SdkContextClass.instance.roomViewStore.getRoomId() || null;
if (!roomId) throw new Error("No room specified and no room in RoomViewStore focus.");
return { client, roomId };
}
public async sendEvent<K extends keyof StateEvents>(
eventType: K,
content: StateEvents[K],
@@ -306,10 +316,7 @@ export class ElementWidgetDriver extends WidgetDriver {
stateKey: string | null = null,
targetRoomId: string | null = null,
): Promise<ISendEventDetails> {
const client = MatrixClientPeg.get();
const roomId = targetRoomId || SdkContextClass.instance.roomViewStore.getRoomId();
if (!client || !roomId) throw new Error("Not in a room or not attached to a client");
const { client, roomId } = this.getSendEventTarget(targetRoomId);
let r: { event_id: string } | null;
if (stateKey !== null) {
@@ -348,6 +355,42 @@ export class ElementWidgetDriver extends WidgetDriver {
return { roomId, eventId: r.event_id };
}
/**
* @experimental Part of MSC4354
* @see {@link WidgetDriver#sendStickyEvent}
*/
public async sendStickyEvent(
stickyDurationMs: number,
eventType: string,
content: unknown,
targetRoomId?: string | null,
): Promise<ISendEventDetails> {
const { client, roomId } = this.getSendEventTarget(targetRoomId);
const r = await client._unstable_sendStickyEvent(
roomId,
stickyDurationMs,
null,
eventType as keyof TimelineEvents,
content as TimelineEvents[keyof TimelineEvents] & { msc4354_sticky_key: string },
);
return { roomId, eventId: r.event_id };
}
private getSendDelayedEventOpts(delay: number | null, parentDelayId: string | null): SendDelayedEventRequestOpts {
if (delay !== null) {
return {
delay,
...(parentDelayId !== null && { parent_delay_id: parentDelayId }),
};
} else if (parentDelayId !== null) {
return {
parent_delay_id: parentDelayId,
};
}
throw new Error("Must provide at least one of delay or parentDelayId");
}
/**
* @experimental Part of MSC4140 & MSC4157
* @see {@link WidgetDriver#sendDelayedEvent}
@@ -379,24 +422,8 @@ export class ElementWidgetDriver extends WidgetDriver {
stateKey: string | null = null,
targetRoomId: string | null = null,
): Promise<ISendDelayedEventDetails> {
const client = MatrixClientPeg.get();
const roomId = targetRoomId || SdkContextClass.instance.roomViewStore.getRoomId();
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");
}
const { client, roomId } = this.getSendEventTarget(targetRoomId);
const delayOpts = this.getSendDelayedEventOpts(delay, parentDelayId);
let r: SendDelayedEventResponse | null;
if (stateKey !== null) {
@@ -425,13 +452,39 @@ export class ElementWidgetDriver extends WidgetDriver {
};
}
/**
* @experimental Part of MSC4354
* @see {@link WidgetDriver#sendStickyEvent}
*/
public async sendDelayedStickyEvent(
delay: number | null,
parentDelayId: string | null,
stickyDurationMs: number,
eventType: string,
content: unknown,
targetRoomId?: string | null,
): Promise<ISendDelayedEventDetails> {
const { client, roomId } = this.getSendEventTarget(targetRoomId);
const delayOpts = this.getSendDelayedEventOpts(delay, parentDelayId);
const r = await client._unstable_sendStickyDelayedEvent(
roomId,
stickyDurationMs,
delayOpts,
null,
eventType as keyof TimelineEvents,
content as TimelineEvents[keyof TimelineEvents] & { msc4354_sticky_key: string },
);
return { roomId, delayId: r.delay_id };
}
/**
* @experimental Part of MSC4140 & MSC4157
*/
public async cancelScheduledDelayedEvent(delayId: string): Promise<void> {
const client = MatrixClientPeg.get();
if (!client) throw new Error("Not in a room or not attached to a client");
if (!client) throw new Error("Not attached to a client");
await client._unstable_cancelScheduledDelayedEvent(delayId);
}
@@ -442,7 +495,7 @@ export class ElementWidgetDriver extends WidgetDriver {
public async restartScheduledDelayedEvent(delayId: string): Promise<void> {
const client = MatrixClientPeg.get();
if (!client) throw new Error("Not in a room or not attached to a client");
if (!client) throw new Error("Not attached to a client");
await client._unstable_restartScheduledDelayedEvent(delayId);
}
@@ -453,7 +506,7 @@ export class ElementWidgetDriver extends WidgetDriver {
public async sendScheduledDelayedEvent(delayId: string): Promise<void> {
const client = MatrixClientPeg.get();
if (!client) throw new Error("Not in a room or not attached to a client");
if (!client) throw new Error("Not attached to a client");
await client._unstable_sendScheduledDelayedEvent(delayId);
}

View File

@@ -540,10 +540,8 @@ export class WidgetMessaging extends TypedEventEmitter<WidgetMessagingEvent, Wid
});
};
private onToDeviceMessage = async (payload: ReceivedToDeviceMessage): Promise<void> => {
const { message, encryptionInfo } = payload;
// TODO: Update the widget API to use a proper IToDeviceMessage instead of a IRoomEvent
await this.widgetApi?.feedToDevice(message as IRoomEvent, encryptionInfo != null);
private onToDeviceMessage = async ({ message, encryptionInfo }: ReceivedToDeviceMessage): Promise<void> => {
await this.widgetApi?.feedToDevice(message, encryptionInfo != null);
};
/**