Skip to content

Commit 5b32803

Browse files
authored
Merge pull request #34 from matrix-org/travis/msc2876
Support MSC2876: Reading events from rooms
2 parents cd3e9bc + e6ea069 commit 5b32803

File tree

8 files changed

+182
-0
lines changed

8 files changed

+182
-0
lines changed

src/ClientWidgetApi.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import {
6060
import { SimpleObservable } from "./util/SimpleObservable";
6161
import { IOpenIDCredentialsActionRequestData } from "./interfaces/OpenIDCredentialsAction";
6262
import { INavigateActionRequest } from "./interfaces/NavigateAction";
63+
import { IReadEventFromWidgetActionRequest, IReadEventFromWidgetResponseData } from "./interfaces/ReadEventAction";
6364

6465
/**
6566
* API handler for the client side of widgets. This raises events
@@ -156,6 +157,16 @@ export class ClientWidgetApi extends EventEmitter {
156157
e.matchesAsStateEvent(eventType, stateKey) && e.direction === EventDirection.Receive);
157158
}
158159

160+
public canReadRoomEvent(eventType: string, msgtype: string = null): boolean {
161+
return this.allowedEvents.some(e =>
162+
e.matchesAsRoomEvent(eventType, msgtype) && e.direction === EventDirection.Read);
163+
}
164+
165+
public canReadStateEvent(eventType: string, stateKey: string): boolean {
166+
return this.allowedEvents.some(e =>
167+
e.matchesAsStateEvent(eventType, stateKey) && e.direction === EventDirection.Read);
168+
}
169+
159170
public stop() {
160171
this.isStopped = true;
161172
this.transport.stop();
@@ -331,6 +342,41 @@ export class ClientWidgetApi extends EventEmitter {
331342
this.driver.askOpenID(observer);
332343
}
333344

345+
private handleReadEvents(request: IReadEventFromWidgetActionRequest) {
346+
if (!request.data.type) {
347+
return this.transport.reply<IWidgetApiErrorResponseData>(request, {
348+
error: {message: "Invalid request - missing event type"},
349+
});
350+
}
351+
if (request.data.limit !== undefined && (!request.data.limit || request.data.limit < 0)) {
352+
return this.transport.reply<IWidgetApiErrorResponseData>(request, {
353+
error: {message: "Invalid request - limit out of range"},
354+
});
355+
}
356+
357+
const limit = request.data.limit || 0;
358+
359+
let events: Promise<unknown[]> = Promise.resolve([]);
360+
if (request.data.state_key !== undefined) {
361+
const stateKey = request.data.state_key === true ? undefined : request.data.state_key.toString();
362+
if (!this.canReadStateEvent(request.data.type, stateKey)) {
363+
return this.transport.reply<IWidgetApiErrorResponseData>(request, {
364+
error: {message: "Cannot read state events of this type"},
365+
});
366+
}
367+
events = this.driver.readStateEvents(request.data.type, stateKey, limit);
368+
} else {
369+
if (!this.canReadRoomEvent(request.data.type, request.data.msgtype)) {
370+
return this.transport.reply<IWidgetApiErrorResponseData>(request, {
371+
error: {message: "Cannot read room events of this type"},
372+
});
373+
}
374+
events = this.driver.readRoomEvents(request.data.type, request.data.msgtype, limit);
375+
}
376+
377+
return events.then(evs => this.transport.reply<IReadEventFromWidgetResponseData>(request, {events: evs}));
378+
}
379+
334380
private handleSendEvent(request: ISendEventFromWidgetActionRequest) {
335381
if (!request.data.type) {
336382
return this.transport.reply<IWidgetApiErrorResponseData>(request, {
@@ -402,6 +448,8 @@ export class ClientWidgetApi extends EventEmitter {
402448
return this.handleNavigate(<INavigateActionRequest>ev.detail);
403449
case WidgetApiFromWidgetAction.MSC2974RenegotiateCapabilities:
404450
return this.handleCapabilitiesRenegotiate(<IRenegotiateCapabilitiesActionRequest>ev.detail);
451+
case WidgetApiFromWidgetAction.MSC2876ReadEvents:
452+
return this.handleReadEvents(<IReadEventFromWidgetActionRequest>ev.detail);
405453
default:
406454
return this.transport.reply(ev.detail, <IWidgetApiErrorResponseData>{
407455
error: {

src/WidgetApi.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import { ISetModalButtonEnabledActionRequestData } from "./interfaces/SetModalBu
5555
import { ISendEventFromWidgetRequestData, ISendEventFromWidgetResponseData } from "./interfaces/SendEventAction";
5656
import { EventDirection, WidgetEventCapability } from "./models/WidgetEventCapability";
5757
import { INavigateActionRequestData } from "./interfaces/NavigateAction";
58+
import { IReadEventFromWidgetRequestData, IReadEventFromWidgetResponseData } from "./interfaces/ReadEventAction";
5859

5960
/**
6061
* API handler for widgets. This raises events for each action
@@ -166,6 +167,18 @@ export class WidgetApi extends EventEmitter {
166167
this.requestCapability(WidgetEventCapability.forStateEvent(EventDirection.Receive, eventType, stateKey).raw);
167168
}
168169

170+
/**
171+
* Requests the capability to read a given state event with optional explicit
172+
* state key. It is not guaranteed to be allowed, but will be asked for if the
173+
* negotiation has not already happened.
174+
* @param {string} eventType The state event type to ask for.
175+
* @param {string} stateKey If specified, the specific state key to request.
176+
* Otherwise all state keys will be requested.
177+
*/
178+
public requestCapabilityToReadState(eventType: string, stateKey?: string) {
179+
this.requestCapability(WidgetEventCapability.forStateEvent(EventDirection.Read, eventType, stateKey).raw);
180+
}
181+
169182
/**
170183
* Requests the capability to send a given room event. It is not guaranteed to be
171184
* allowed, but will be asked for if the negotiation has not already happened.
@@ -184,6 +197,15 @@ export class WidgetApi extends EventEmitter {
184197
this.requestCapability(WidgetEventCapability.forRoomEvent(EventDirection.Receive, eventType).raw);
185198
}
186199

200+
/**
201+
* Requests the capability to read a given room event. It is not guaranteed to be allowed,
202+
* but will be asked for if the negotiation has not already happened.
203+
* @param {string} eventType The room event type to ask for.
204+
*/
205+
public requestCapabilityToReadEvent(eventType: string) {
206+
this.requestCapability(WidgetEventCapability.forRoomEvent(EventDirection.Read, eventType).raw);
207+
}
208+
187209
/**
188210
* Requests the capability to send a given message event with optional explicit
189211
* `msgtype`. It is not guaranteed to be allowed, but will be asked for if the
@@ -206,6 +228,17 @@ export class WidgetApi extends EventEmitter {
206228
this.requestCapability(WidgetEventCapability.forRoomMessageEvent(EventDirection.Receive, msgtype).raw);
207229
}
208230

231+
/**
232+
* Requests the capability to read a given message event with optional explicit
233+
* `msgtype`. It is not guaranteed to be allowed, but will be asked for if the
234+
* negotiation has not already happened.
235+
* @param {string} msgtype If specified, the specific msgtype to request.
236+
* Otherwise all message types will be requested.
237+
*/
238+
public requestCapabilityToReadMessage(msgtype?: string) {
239+
this.requestCapability(WidgetEventCapability.forRoomMessageEvent(EventDirection.Read, msgtype).raw);
240+
}
241+
209242
/**
210243
* Requests an OpenID Connect token from the client for the currently logged in
211244
* user. This token can be validated server-side with the federation API. Note
@@ -344,6 +377,20 @@ export class WidgetApi extends EventEmitter {
344377
);
345378
}
346379

380+
public readRoomEvents(eventType: string, limit = 25, msgtype?: string): Promise<unknown> {
381+
return this.transport.send<IReadEventFromWidgetRequestData, IReadEventFromWidgetResponseData>(
382+
WidgetApiFromWidgetAction.MSC2876ReadEvents,
383+
{type: eventType, msgtype: msgtype, limit},
384+
).then(r => r.events);
385+
}
386+
387+
public readStateEvents(eventType: string, limit = 25, stateKey?: string): Promise<unknown> {
388+
return this.transport.send<IReadEventFromWidgetRequestData, IReadEventFromWidgetResponseData>(
389+
WidgetApiFromWidgetAction.MSC2876ReadEvents,
390+
{type: eventType, state_key: stateKey === undefined ? true : stateKey, limit},
391+
).then(r => r.events);
392+
}
393+
347394
/**
348395
* Sets a button as disabled or enabled on the modal widget. Buttons are enabled by default.
349396
* @param {ModalButtonID} buttonId The button ID to enable/disable.

src/driver/WidgetDriver.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,36 @@ export abstract class WidgetDriver {
6767
return Promise.reject(new Error("Failed to override function"));
6868
}
6969

70+
/**
71+
* Reads all events of the given type, and optionally `msgtype` (if applicable/defined),
72+
* the user has access to. The widget API will have already verified that the widget is
73+
* capable of receiving the events. Less events than the limit are allowed to be returned,
74+
* but not more.
75+
* @param eventType The event type to be read.
76+
* @param msgtype The msgtype of the events to be read, if applicable/defined.
77+
* @param limit The maximum number of events to retrieve. Will be zero to denote "as many
78+
* as possible".
79+
* @returns {Promise<*[]>} Resolves to the room events, or an empty array.
80+
*/
81+
public readRoomEvents(eventType: string, msgtype: string | undefined, limit: number): Promise<unknown[]> {
82+
return Promise.resolve([]);
83+
}
84+
85+
/**
86+
* Reads all events of the given type, and optionally state key (if applicable/defined),
87+
* the user has access to. The widget API will have already verified that the widget is
88+
* capable of receiving the events. Less events than the limit are allowed to be returned,
89+
* but not more.
90+
* @param eventType The event type to be read.
91+
* @param stateKey The state key of the events to be read, if applicable/defined.
92+
* @param limit The maximum number of events to retrieve. Will be zero to denote "as many
93+
* as possible".
94+
* @returns {Promise<*[]>} Resolves to the state events, or an empty array.
95+
*/
96+
public readStateEvents(eventType: string, stateKey: string | undefined, limit: number): Promise<unknown[]> {
97+
return Promise.resolve([]);
98+
}
99+
70100
/**
71101
* Asks the user for permission to validate their identity through OpenID Connect. The
72102
* interface for this function is an observable which accepts the state machine of the

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export * from "./interfaces/ModalWidgetActions";
5050
export * from "./interfaces/SetModalButtonEnabledAction";
5151
export * from "./interfaces/WidgetConfigAction";
5252
export * from "./interfaces/SendEventAction";
53+
export * from "./interfaces/ReadEventAction";
5354
export * from "./interfaces/IRoomEvent";
5455
export * from "./interfaces/NavigateAction";
5556

src/interfaces/ApiVersion.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export enum UnstableApiVersion {
2525
MSC2871 = "org.matrix.msc2871",
2626
MSC2931 = "org.matrix.msc2931",
2727
MSC2974 = "org.matrix.msc2974",
28+
MSC2876 = "org.matrix.msc2876",
2829
}
2930

3031
export type ApiVersion = MatrixApiVersion | UnstableApiVersion | string;
@@ -37,4 +38,5 @@ export const CurrentApiVersions: ApiVersion[] = [
3738
UnstableApiVersion.MSC2871,
3839
UnstableApiVersion.MSC2931,
3940
UnstableApiVersion.MSC2974,
41+
UnstableApiVersion.MSC2876,
4042
];

src/interfaces/ReadEventAction.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2021 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+
import { IWidgetApiRequest, IWidgetApiRequestData } from "./IWidgetApiRequest";
18+
import { WidgetApiFromWidgetAction } from "./WidgetApiAction";
19+
import { IWidgetApiResponseData } from "./IWidgetApiResponse";
20+
21+
export interface IReadEventFromWidgetRequestData extends IWidgetApiRequestData {
22+
state_key?: string | boolean; // eslint-disable-line camelcase
23+
msgtype?: string;
24+
type: string;
25+
limit?: number;
26+
}
27+
28+
export interface IReadEventFromWidgetActionRequest extends IWidgetApiRequest {
29+
action: WidgetApiFromWidgetAction.MSC2876ReadEvents;
30+
data: IReadEventFromWidgetRequestData;
31+
}
32+
33+
export interface IReadEventFromWidgetResponseData extends IWidgetApiResponseData {
34+
events: unknown[];
35+
}
36+
37+
export interface IReadEventFromWidgetActionResponse extends IReadEventFromWidgetActionRequest {
38+
response: IReadEventFromWidgetResponseData;
39+
}

src/interfaces/WidgetApiAction.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ export enum WidgetApiFromWidgetAction {
3838
SetModalButtonEnabled = "set_button_enabled",
3939
SendEvent = "send_event",
4040

41+
/**
42+
* @deprecated It is not recommended to rely on this existing - it can be removed without notice.
43+
*/
44+
MSC2876ReadEvents = "org.matrix.msc2876.read_events",
45+
4146
/**
4247
* @deprecated It is not recommended to rely on this existing - it can be removed without notice.
4348
*/

src/models/WidgetEventCapability.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { Capability } from "..";
1919
export enum EventDirection {
2020
Send = "send",
2121
Receive = "receive",
22+
Read = "read",
2223
}
2324

2425
export class WidgetEventCapability {
@@ -123,6 +124,15 @@ export class WidgetEventCapability {
123124
isState = true;
124125
eventSegment = cap.substring("org.matrix.msc2762.receive.state_event:".length);
125126
}
127+
} else if (cap.startsWith("org.matrix.msc2762.read.")) {
128+
if (cap.startsWith("org.matrix.msc2762.read.event:")) {
129+
direction = EventDirection.Read;
130+
eventSegment = cap.substring("org.matrix.msc2762.read.event:".length);
131+
} else if (cap.startsWith("org.matrix.msc2762.read.state_event:")) {
132+
direction = EventDirection.Read;
133+
isState = true;
134+
eventSegment = cap.substring("org.matrix.msc2762.read.state_event:".length);
135+
}
126136
}
127137

128138
if (direction === null) continue;

0 commit comments

Comments
 (0)