Skip to content

Commit 878ebff

Browse files
authored
Fix types for to device messages (#153)
* Fix toDevice message type * Fix existing content types. * lint * Add and update tests * whoops remove only * Add a test for a discarded message.
1 parent 1b96056 commit 878ebff

File tree

6 files changed

+73
-17
lines changed

6 files changed

+73
-17
lines changed

src/ClientWidgetApi.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ import {
109109
} from "./interfaces/DownloadFileAction";
110110
import { IThemeChangeActionRequestData } from "./interfaces/ThemeChangeAction";
111111
import { IUpdateStateToWidgetRequestData } from "./interfaces/UpdateStateAction";
112+
import { IToDeviceMessage } from "./interfaces/IToDeviceMessage";
112113

113114
/**
114115
* API handler for the client side of widgets. This raises events
@@ -1153,13 +1154,12 @@ export class ClientWidgetApi extends EventEmitter {
11531154
* able to receive the event due to permissions, rejects if the widget
11541155
* failed to handle the event.
11551156
*/
1156-
public async feedToDevice(rawEvent: IRoomEvent, encrypted: boolean): Promise<void> {
1157-
if (this.canReceiveToDeviceEvent(rawEvent.type)) {
1158-
await this.transport.send<ISendToDeviceToWidgetRequestData>(
1159-
WidgetApiToWidgetAction.SendToDevice,
1160-
// it's compatible, but missing the index signature
1161-
{ ...rawEvent, encrypted } as ISendToDeviceToWidgetRequestData,
1162-
);
1157+
public async feedToDevice(message: IToDeviceMessage, encrypted: boolean): Promise<void> {
1158+
if (this.canReceiveToDeviceEvent(message.type)) {
1159+
await this.transport.send<ISendToDeviceToWidgetRequestData>(WidgetApiToWidgetAction.SendToDevice, {
1160+
...message,
1161+
encrypted,
1162+
});
11631163
}
11641164
}
11651165

src/interfaces/IRoomAccountData.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@
1717
export interface IRoomAccountData {
1818
type: string;
1919
room_id: string; // eslint-disable-line camelcase
20-
content: unknown;
20+
content: Record<string, unknown>;
2121
}

src/interfaces/IRoomEvent.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ export interface IRoomEvent {
2121
room_id: string; // eslint-disable-line camelcase
2222
state_key?: string; // eslint-disable-line camelcase
2323
origin_server_ts: number; // eslint-disable-line camelcase
24-
content: unknown;
25-
unsigned: unknown;
24+
content: Record<string, unknown>;
25+
unsigned: Record<string, unknown>;
2626
//MSC4354
2727
sticky?: {
2828
duration_ms: number;

src/interfaces/IToDeviceMessage.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright 2025 The Matrix.org Foundation C.I.C.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
export interface IToDeviceMessage {
18+
content: Record<string, unknown>;
19+
sender: string;
20+
type: string;
21+
}

src/interfaces/SendToDeviceAction.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import { IWidgetApiRequest, IWidgetApiRequestData } from "./IWidgetApiRequest";
1818
import { WidgetApiFromWidgetAction, WidgetApiToWidgetAction } from "./WidgetApiAction";
1919
import { IWidgetApiResponseData } from "./IWidgetApiResponse";
20-
import { IRoomEvent } from "./IRoomEvent";
20+
import { IToDeviceMessage } from "./IToDeviceMessage";
2121

2222
export interface ISendToDeviceFromWidgetRequestData extends IWidgetApiRequestData {
2323
type: string;
@@ -38,7 +38,7 @@ export interface ISendToDeviceFromWidgetActionResponse extends ISendToDeviceFrom
3838
response: ISendToDeviceFromWidgetResponseData;
3939
}
4040

41-
export interface ISendToDeviceToWidgetRequestData extends IWidgetApiRequestData, IRoomEvent {
41+
export interface ISendToDeviceToWidgetRequestData extends IWidgetApiRequestData, IToDeviceMessage {
4242
encrypted: boolean;
4343
}
4444

test/ClientWidgetApi-test.ts

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import {
4848
} from "../src";
4949
import { IGetMediaConfigActionFromWidgetActionRequest } from "../src/interfaces/GetMediaConfigAction";
5050
import { IReadRoomAccountDataFromWidgetActionRequest } from "../src/interfaces/ReadRoomAccountDataAction";
51+
import { IToDeviceMessage } from "../src/interfaces/IToDeviceMessage";
5152

5253
jest.mock("../src/transport/PostmessageTransport");
5354

@@ -886,11 +887,11 @@ describe("ClientWidgetApi", () => {
886887
describe("receiving events", () => {
887888
const roomId = "!room:example.org";
888889
const otherRoomId = "!other-room:example.org";
889-
const event = createRoomEvent({ room_id: roomId, type: "m.room.message", content: "hello" });
890+
const event = createRoomEvent({ room_id: roomId, type: "m.room.message", content: { hello: "there" } });
890891
const eventFromOtherRoom = createRoomEvent({
891892
room_id: otherRoomId,
892893
type: "m.room.message",
893-
content: "test",
894+
content: { test: "test" },
894895
});
895896

896897
it("forwards events to the widget from one room only", async () => {
@@ -1095,6 +1096,36 @@ describe("ClientWidgetApi", () => {
10951096
});
10961097
});
10971098

1099+
describe("receiving to device messages", () => {
1100+
it.each([true, false])("forwards device messages to the widget", async (encrypted) => {
1101+
const event: IToDeviceMessage = {
1102+
content: { foo: "bar" },
1103+
type: "org.example.mytype",
1104+
sender: "@alice:example.org",
1105+
};
1106+
// Give the widget capabilities to receive from just one room
1107+
await loadIframe(["org.matrix.msc3819.receive.to_device:org.example.mytype"]);
1108+
1109+
// Event from the matching room should be forwarded
1110+
await clientWidgetApi.feedToDevice(event, encrypted);
1111+
expect(transport.send).toHaveBeenCalledWith(WidgetApiToWidgetAction.SendToDevice, { ...event, encrypted });
1112+
});
1113+
it("ignores messages not allowed by capabilities", async () => {
1114+
const event: IToDeviceMessage = {
1115+
content: { foo: "bar" },
1116+
type: "org.example.othertype",
1117+
sender: "@alice:example.org",
1118+
};
1119+
// Give the widget capabilities to receive from just one room
1120+
await loadIframe(["org.matrix.msc3819.receive.to_device:org.example.mytype"]);
1121+
// Clear all prior messages.
1122+
jest.mocked(transport.send).mockClear();
1123+
// Event from the matching room should be forwarded
1124+
await clientWidgetApi.feedToDevice(event, false);
1125+
expect(transport.send).not.toHaveBeenCalled();
1126+
});
1127+
});
1128+
10981129
describe("update_delayed_event action", () => {
10991130
it("fails to cancel delayed events", async () => {
11001131
const event: IUpdateDelayedEventFromWidgetActionRequest = {
@@ -1727,7 +1758,7 @@ describe("ClientWidgetApi", () => {
17271758
it("reads events from a specific room", async () => {
17281759
const roomId = "!room:example.org";
17291760
jest.spyOn(clientWidgetApi, "getWidgetVersions").mockResolvedValue([]);
1730-
const event = createRoomEvent({ room_id: roomId, type: "net.example.test", content: "test" });
1761+
const event = createRoomEvent({ room_id: roomId, type: "net.example.test", content: { test: "test" } });
17311762
driver.readRoomTimeline.mockImplementation(async (rId) => {
17321763
if (rId === roomId) return [event];
17331764
return [];
@@ -1772,8 +1803,12 @@ describe("ClientWidgetApi", () => {
17721803
const roomId = "!room:example.org";
17731804
const otherRoomId = "!other-room:example.org";
17741805
jest.spyOn(clientWidgetApi, "getWidgetVersions").mockResolvedValue([]);
1775-
const event = createRoomEvent({ room_id: roomId, type: "net.example.test", content: "test" });
1776-
const otherRoomEvent = createRoomEvent({ room_id: otherRoomId, type: "net.example.test", content: "hi" });
1806+
const event = createRoomEvent({ room_id: roomId, type: "net.example.test", content: { test: "test" } });
1807+
const otherRoomEvent = createRoomEvent({
1808+
room_id: otherRoomId,
1809+
type: "net.example.test",
1810+
content: { hi: "there" },
1811+
});
17771812
driver.getKnownRooms.mockReturnValue([roomId, otherRoomId]);
17781813
driver.readRoomTimeline.mockImplementation(async (rId) => {
17791814
if (rId === roomId) return [event];

0 commit comments

Comments
 (0)