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:
+1
-1
@@ -130,7 +130,7 @@
|
|||||||
"maplibre-gl": "^5.0.0",
|
"maplibre-gl": "^5.0.0",
|
||||||
"matrix-encrypt-attachment": "^1.0.3",
|
"matrix-encrypt-attachment": "^1.0.3",
|
||||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
||||||
"matrix-widget-api": "^1.14.0",
|
"matrix-widget-api": "^1.15.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",
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ import {
|
|||||||
type SendDelayedEventResponse,
|
type SendDelayedEventResponse,
|
||||||
type StateEvents,
|
type StateEvents,
|
||||||
type TimelineEvents,
|
type TimelineEvents,
|
||||||
|
type SendDelayedEventRequestOpts,
|
||||||
|
type MatrixClient,
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} from "matrix-js-sdk/src/matrix";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import {
|
import {
|
||||||
@@ -122,6 +124,7 @@ export class ElementWidgetDriver extends WidgetDriver {
|
|||||||
this.allowedCapabilities.add(`org.matrix.msc2762.timeline:${inRoomId}`);
|
this.allowedCapabilities.add(`org.matrix.msc2762.timeline:${inRoomId}`);
|
||||||
this.allowedCapabilities.add(MatrixCapabilities.MSC4157SendDelayedEvent);
|
this.allowedCapabilities.add(MatrixCapabilities.MSC4157SendDelayedEvent);
|
||||||
this.allowedCapabilities.add(MatrixCapabilities.MSC4157UpdateDelayedEvent);
|
this.allowedCapabilities.add(MatrixCapabilities.MSC4157UpdateDelayedEvent);
|
||||||
|
this.allowedCapabilities.add(MatrixCapabilities.MSC4354SendStickyEvent);
|
||||||
|
|
||||||
this.allowedCapabilities.add(
|
this.allowedCapabilities.add(
|
||||||
WidgetEventCapability.forStateEvent(EventDirection.Receive, EventType.RoomName).raw,
|
WidgetEventCapability.forStateEvent(EventDirection.Receive, EventType.RoomName).raw,
|
||||||
@@ -288,6 +291,13 @@ export class ElementWidgetDriver extends WidgetDriver {
|
|||||||
return allAllowed;
|
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>(
|
public async sendEvent<K extends keyof StateEvents>(
|
||||||
eventType: K,
|
eventType: K,
|
||||||
content: StateEvents[K],
|
content: StateEvents[K],
|
||||||
@@ -306,10 +316,7 @@ export class ElementWidgetDriver extends WidgetDriver {
|
|||||||
stateKey: string | null = null,
|
stateKey: string | null = null,
|
||||||
targetRoomId: string | null = null,
|
targetRoomId: string | null = null,
|
||||||
): Promise<ISendEventDetails> {
|
): Promise<ISendEventDetails> {
|
||||||
const client = MatrixClientPeg.get();
|
const { client, roomId } = this.getSendEventTarget(targetRoomId);
|
||||||
const roomId = targetRoomId || SdkContextClass.instance.roomViewStore.getRoomId();
|
|
||||||
|
|
||||||
if (!client || !roomId) throw new Error("Not in a room or not attached to a client");
|
|
||||||
|
|
||||||
let r: { event_id: string } | null;
|
let r: { event_id: string } | null;
|
||||||
if (stateKey !== null) {
|
if (stateKey !== null) {
|
||||||
@@ -348,6 +355,42 @@ export class ElementWidgetDriver extends WidgetDriver {
|
|||||||
return { roomId, eventId: r.event_id };
|
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
|
* @experimental Part of MSC4140 & MSC4157
|
||||||
* @see {@link WidgetDriver#sendDelayedEvent}
|
* @see {@link WidgetDriver#sendDelayedEvent}
|
||||||
@@ -379,24 +422,8 @@ export class ElementWidgetDriver extends WidgetDriver {
|
|||||||
stateKey: string | null = null,
|
stateKey: string | null = null,
|
||||||
targetRoomId: string | null = null,
|
targetRoomId: string | null = null,
|
||||||
): Promise<ISendDelayedEventDetails> {
|
): Promise<ISendDelayedEventDetails> {
|
||||||
const client = MatrixClientPeg.get();
|
const { client, roomId } = this.getSendEventTarget(targetRoomId);
|
||||||
const roomId = targetRoomId || SdkContextClass.instance.roomViewStore.getRoomId();
|
const delayOpts = this.getSendDelayedEventOpts(delay, parentDelayId);
|
||||||
|
|
||||||
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;
|
let r: SendDelayedEventResponse | null;
|
||||||
if (stateKey !== 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
|
* @experimental Part of MSC4140 & MSC4157
|
||||||
*/
|
*/
|
||||||
public async cancelScheduledDelayedEvent(delayId: string): Promise<void> {
|
public async cancelScheduledDelayedEvent(delayId: string): Promise<void> {
|
||||||
const client = MatrixClientPeg.get();
|
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);
|
await client._unstable_cancelScheduledDelayedEvent(delayId);
|
||||||
}
|
}
|
||||||
@@ -442,7 +495,7 @@ export class ElementWidgetDriver extends WidgetDriver {
|
|||||||
public async restartScheduledDelayedEvent(delayId: string): Promise<void> {
|
public async restartScheduledDelayedEvent(delayId: string): Promise<void> {
|
||||||
const client = MatrixClientPeg.get();
|
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);
|
await client._unstable_restartScheduledDelayedEvent(delayId);
|
||||||
}
|
}
|
||||||
@@ -453,7 +506,7 @@ export class ElementWidgetDriver extends WidgetDriver {
|
|||||||
public async sendScheduledDelayedEvent(delayId: string): Promise<void> {
|
public async sendScheduledDelayedEvent(delayId: string): Promise<void> {
|
||||||
const client = MatrixClientPeg.get();
|
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);
|
await client._unstable_sendScheduledDelayedEvent(delayId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -540,10 +540,8 @@ export class WidgetMessaging extends TypedEventEmitter<WidgetMessagingEvent, Wid
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private onToDeviceMessage = async (payload: ReceivedToDeviceMessage): Promise<void> => {
|
private onToDeviceMessage = async ({ message, encryptionInfo }: ReceivedToDeviceMessage): Promise<void> => {
|
||||||
const { message, encryptionInfo } = payload;
|
await this.widgetApi?.feedToDevice(message, encryptionInfo != null);
|
||||||
// TODO: Update the widget API to use a proper IToDeviceMessage instead of a IRoomEvent
|
|
||||||
await this.widgetApi?.feedToDevice(message as IRoomEvent, encryptionInfo != null);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -308,6 +308,8 @@ export function createTestClient(): MatrixClient {
|
|||||||
_unstable_cancelScheduledDelayedEvent: jest.fn(),
|
_unstable_cancelScheduledDelayedEvent: jest.fn(),
|
||||||
_unstable_restartScheduledDelayedEvent: jest.fn(),
|
_unstable_restartScheduledDelayedEvent: jest.fn(),
|
||||||
_unstable_sendScheduledDelayedEvent: jest.fn(),
|
_unstable_sendScheduledDelayedEvent: jest.fn(),
|
||||||
|
_unstable_sendStickyEvent: jest.fn(),
|
||||||
|
_unstable_sendStickyDelayedEvent: jest.fn(),
|
||||||
|
|
||||||
searchUserDirectory: jest.fn().mockResolvedValue({ limited: false, results: [] }),
|
searchUserDirectory: jest.fn().mockResolvedValue({ limited: false, results: [] }),
|
||||||
setDeviceVerified: jest.fn(),
|
setDeviceVerified: jest.fn(),
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ describe("ElementWidgetDriver", () => {
|
|||||||
"org.matrix.msc3819.receive.to_device:m.call.replaces",
|
"org.matrix.msc3819.receive.to_device:m.call.replaces",
|
||||||
"org.matrix.msc4157.send.delayed_event",
|
"org.matrix.msc4157.send.delayed_event",
|
||||||
"org.matrix.msc4157.update_delayed_event",
|
"org.matrix.msc4157.update_delayed_event",
|
||||||
|
"org.matrix.msc4354.send_sticky_event",
|
||||||
// RTC decline events (send/receive, unstable/stable)
|
// RTC decline events (send/receive, unstable/stable)
|
||||||
"org.matrix.msc2762.send.event:org.matrix.msc4310.rtc.decline",
|
"org.matrix.msc2762.send.event:org.matrix.msc4310.rtc.decline",
|
||||||
"org.matrix.msc2762.send.event:m.rtc.decline",
|
"org.matrix.msc2762.send.event:m.rtc.decline",
|
||||||
@@ -593,6 +594,84 @@ describe("ElementWidgetDriver", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("sendStickyEvent", () => {
|
||||||
|
let driver: WidgetDriver;
|
||||||
|
const roomId = "!this-room-id";
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
driver = mkDefaultDriver();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sends sticky message events", async () => {
|
||||||
|
client._unstable_sendStickyEvent.mockResolvedValue({
|
||||||
|
event_id: "id",
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(driver.sendStickyEvent(2000, EventType.RoomMessage, {})).resolves.toEqual({
|
||||||
|
roomId,
|
||||||
|
eventId: "id",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(client._unstable_sendStickyEvent).toHaveBeenCalledWith(
|
||||||
|
roomId,
|
||||||
|
2000,
|
||||||
|
null,
|
||||||
|
EventType.RoomMessage,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("sendDelayedStickyEvent", () => {
|
||||||
|
let driver: WidgetDriver;
|
||||||
|
const roomId = "!this-room-id";
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
driver = mkDefaultDriver();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sends delayed sticky message events", async () => {
|
||||||
|
client._unstable_sendStickyDelayedEvent.mockResolvedValue({
|
||||||
|
delay_id: "id",
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(driver.sendDelayedStickyEvent(1000, null, 2000, EventType.RoomMessage, {})).resolves.toEqual({
|
||||||
|
roomId,
|
||||||
|
delayId: "id",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(client._unstable_sendStickyDelayedEvent).toHaveBeenCalledWith(
|
||||||
|
roomId,
|
||||||
|
2000,
|
||||||
|
{ delay: 1000 },
|
||||||
|
null,
|
||||||
|
EventType.RoomMessage,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("sends child action delayed sticky message events", async () => {
|
||||||
|
client._unstable_sendStickyDelayedEvent.mockResolvedValue({
|
||||||
|
delay_id: "id-child",
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
driver.sendDelayedStickyEvent(null, "id-parent", 2000, EventType.RoomMessage, {}),
|
||||||
|
).resolves.toEqual({
|
||||||
|
roomId,
|
||||||
|
delayId: "id-child",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(client._unstable_sendStickyDelayedEvent).toHaveBeenCalledWith(
|
||||||
|
roomId,
|
||||||
|
2000,
|
||||||
|
{ parent_delay_id: "id-parent" },
|
||||||
|
null,
|
||||||
|
EventType.RoomMessage,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("If the feature_dynamic_room_predecessors feature is not enabled", () => {
|
describe("If the feature_dynamic_room_predecessors feature is not enabled", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
|
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
|
||||||
|
|||||||
@@ -1577,17 +1577,8 @@
|
|||||||
yaml "^2.7.0"
|
yaml "^2.7.0"
|
||||||
|
|
||||||
"@element-hq/web-shared-components@link:packages/shared-components":
|
"@element-hq/web-shared-components@link:packages/shared-components":
|
||||||
version "0.0.0-test.12"
|
version "0.0.0"
|
||||||
dependencies:
|
uid ""
|
||||||
"@element-hq/element-web-module-api" "^1.8.0"
|
|
||||||
"@vector-im/compound-design-tokens" "^6.3.0"
|
|
||||||
classnames "^2.5.1"
|
|
||||||
counterpart "^0.18.6"
|
|
||||||
lodash "^4.17.21"
|
|
||||||
matrix-web-i18n "^3.4.0"
|
|
||||||
patch-package "^8.0.1"
|
|
||||||
react-merge-refs "^3.0.2"
|
|
||||||
temporal-polyfill "^0.3.0"
|
|
||||||
|
|
||||||
"@emnapi/core@^1.4.3":
|
"@emnapi/core@^1.4.3":
|
||||||
version "1.7.0"
|
version "1.7.0"
|
||||||
@@ -4223,6 +4214,7 @@
|
|||||||
|
|
||||||
"@vector-im/matrix-wysiwyg-wasm@link:../../../.cache/yarn/v6/npm-@vector-im-matrix-wysiwyg-2.40.0-53c9ca5ea907d91e4515da64f20a82e5586b882c-integrity/node_modules/bindings/wysiwyg-wasm":
|
"@vector-im/matrix-wysiwyg-wasm@link:../../../.cache/yarn/v6/npm-@vector-im-matrix-wysiwyg-2.40.0-53c9ca5ea907d91e4515da64f20a82e5586b882c-integrity/node_modules/bindings/wysiwyg-wasm":
|
||||||
version "0.0.0"
|
version "0.0.0"
|
||||||
|
uid ""
|
||||||
|
|
||||||
"@vector-im/matrix-wysiwyg@2.40.0":
|
"@vector-im/matrix-wysiwyg@2.40.0":
|
||||||
version "2.40.0"
|
version "2.40.0"
|
||||||
@@ -9693,7 +9685,7 @@ matrix-web-i18n@^3.2.1, matrix-web-i18n@^3.4.0:
|
|||||||
minimist "^1.2.8"
|
minimist "^1.2.8"
|
||||||
walk "^2.3.15"
|
walk "^2.3.15"
|
||||||
|
|
||||||
matrix-widget-api@^1.14.0:
|
matrix-widget-api@^1.10.0, matrix-widget-api@^1.15.0:
|
||||||
version "1.15.0"
|
version "1.15.0"
|
||||||
resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.15.0.tgz#a508f72a5993a95382bdf890bd9e54525295b321"
|
resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.15.0.tgz#a508f72a5993a95382bdf890bd9e54525295b321"
|
||||||
integrity sha512-Yu9rX9wyF3A1sqviKgiYHz8aGgL3HhJe9OXKi/lccr1eZnNb6y+ELdbshTjs+VLKM4rkTWt6CE3THsw3f/CZhg==
|
integrity sha512-Yu9rX9wyF3A1sqviKgiYHz8aGgL3HhJe9OXKi/lccr1eZnNb6y+ELdbshTjs+VLKM4rkTWt6CE3THsw3f/CZhg==
|
||||||
|
|||||||
Reference in New Issue
Block a user