Skip to content

Commit cd95117

Browse files
committed
Add support for MSC2871: Notifying the widget of its capabilities
This causes the `ready` event of the `WidgetApi` to change to when the client notifies the widget of its approved capabilities (when the client supports the MSC). MSC2871: matrix-org/matrix-spec-proposals#2871
1 parent 9e63633 commit cd95117

File tree

5 files changed

+90
-10
lines changed

5 files changed

+90
-10
lines changed

src/ClientWidgetApi.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { WidgetApiFromWidgetAction, WidgetApiToWidgetAction } from "./interfaces
2525
import { IWidgetApiErrorResponseData } from "./interfaces/IWidgetApiErrorResponse";
2626
import { Capability } from "./interfaces/Capabilities";
2727
import { ISendEventDetails, WidgetDriver } from "./driver/WidgetDriver";
28-
import { ICapabilitiesActionResponseData } from "./interfaces/CapabilitiesAction";
28+
import { ICapabilitiesActionResponseData, INotifyCapabilitiesActionRequestData } from "./interfaces/CapabilitiesAction";
2929
import {
3030
ISupportedVersionsActionRequest,
3131
ISupportedVersionsActionResponseData,
@@ -163,15 +163,23 @@ export class ClientWidgetApi extends EventEmitter {
163163
// widget has loaded - tell all the listeners that
164164
this.emit("preparing");
165165

166+
let requestedCaps: Capability[];
166167
this.transport.send<IWidgetApiRequestEmptyData, ICapabilitiesActionResponseData>(
167168
WidgetApiToWidgetAction.Capabilities, {},
168169
).then(caps => {
170+
requestedCaps = caps.capabilities;
169171
return this.driver.validateCapabilities(new Set(caps.capabilities));
170172
}).then(allowedCaps => {
171173
console.log(`Widget ${this.widget.id} is allowed capabilities:`, Array.from(allowedCaps));
172174
this.allowedCapabilities = allowedCaps;
173175
this.allowedEvents = WidgetEventCapability.findEventCapabilities(allowedCaps);
174176
this.capabilitiesFinished = true;
177+
this.transport.send(WidgetApiToWidgetAction.NotifyCapabilities, <INotifyCapabilitiesActionRequestData>{
178+
requested: requestedCaps,
179+
approved: Array.from(allowedCaps),
180+
}).catch(e => {
181+
console.warn("non-fatal error notifying widget of approved capabilities:", e);
182+
});
175183
this.emit("ready");
176184
});
177185
}

src/WidgetApi.ts

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,12 @@ import {
2222
ISupportedVersionsActionRequest,
2323
ISupportedVersionsActionResponseData,
2424
} from "./interfaces/SupportedVersionsAction";
25-
import { CurrentApiVersions } from "./interfaces/ApiVersion";
26-
import { ICapabilitiesActionRequest, ICapabilitiesActionResponseData } from "./interfaces/CapabilitiesAction";
25+
import { ApiVersion, CurrentApiVersions, UnstableApiVersion } from "./interfaces/ApiVersion";
26+
import {
27+
ICapabilitiesActionRequest,
28+
ICapabilitiesActionResponseData,
29+
INotifyCapabilitiesActionRequest,
30+
} from "./interfaces/CapabilitiesAction";
2731
import { ITransport } from "./transport/ITransport";
2832
import { PostmessageTransport } from "./transport/PostmessageTransport";
2933
import { WidgetApiFromWidgetAction, WidgetApiToWidgetAction } from "./interfaces/WidgetApiAction";
@@ -39,11 +43,11 @@ import {
3943
import { IOpenIDCredentialsActionRequest } from "./interfaces/OpenIDCredentialsAction";
4044
import { MatrixWidgetType, WidgetType } from "./interfaces/WidgetType";
4145
import {
46+
BuiltInModalButtonID,
4247
IModalWidgetCreateData,
4348
IModalWidgetOpenRequestData,
4449
IModalWidgetOpenRequestDataButton,
4550
IModalWidgetReturnData,
46-
BuiltInModalButtonID,
4751
ModalButtonID,
4852
} from "./interfaces/ModalWidgetActions";
4953
import { ISetModalButtonEnabledActionRequestData } from "./interfaces/SetModalButtonEnabledAction";
@@ -71,6 +75,8 @@ export class WidgetApi extends EventEmitter {
7175

7276
private capabilitiesFinished = false;
7377
private requestedCapabilities: Capability[] = [];
78+
private approvedCapabilities: Capability[];
79+
private cachedClientVersions: ApiVersion[];
7480

7581
/**
7682
* Creates a new API handler for the given widget.
@@ -93,6 +99,20 @@ export class WidgetApi extends EventEmitter {
9399
this.transport.on("message", this.handleMessage.bind(this));
94100
}
95101

102+
/**
103+
* Determines if the widget was granted a particular capability. Note that on
104+
* clients where the capabilities are not fed back to the widget this function
105+
* will rely on requested capabilities instead.
106+
* @param {Capability} capability The capability to check for approval of.
107+
* @returns {boolean} True if the widget has approval for the given capability.
108+
*/
109+
public hasCapability(capability: Capability): boolean {
110+
if (Array.isArray(this.approvedCapabilities)) {
111+
return this.approvedCapabilities.includes(capability);
112+
}
113+
return this.requestedCapabilities.includes(capability);
114+
}
115+
96116
/**
97117
* Request a capability from the client. It is not guaranteed to be allowed,
98118
* but will be asked for if the negotiation has not already happened.
@@ -277,6 +297,8 @@ export class WidgetApi extends EventEmitter {
277297
return this.handleCapabilities(<ICapabilitiesActionRequest>ev.detail);
278298
case WidgetApiToWidgetAction.UpdateVisibility:
279299
return this.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{}); // ack to avoid error spam
300+
case WidgetApiToWidgetAction.NotifyCapabilities:
301+
return this.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{}); // ack to avoid error spam
280302
default:
281303
return this.transport.reply(ev.detail, <IWidgetApiErrorResponseData>{
282304
error: {
@@ -293,6 +315,22 @@ export class WidgetApi extends EventEmitter {
293315
});
294316
}
295317

318+
private getClientVersions(): Promise<ApiVersion[]> {
319+
if (Array.isArray(this.cachedClientVersions)) {
320+
return Promise.resolve(this.cachedClientVersions);
321+
}
322+
323+
return this.transport.send<IWidgetApiRequestEmptyData, ISupportedVersionsActionResponseData>(
324+
WidgetApiFromWidgetAction.SupportedApiVersions, {},
325+
).then(r => {
326+
this.cachedClientVersions = r.supported_versions;
327+
return r.supported_versions;
328+
}).catch(e => {
329+
console.warn("non-fatal error getting supported client versions: ", e);
330+
return [];
331+
});
332+
}
333+
296334
private handleCapabilities(request: ICapabilitiesActionRequest) {
297335
if (this.capabilitiesFinished) {
298336
return this.transport.reply<IWidgetApiErrorResponseData>(request, {
@@ -301,10 +339,27 @@ export class WidgetApi extends EventEmitter {
301339
},
302340
});
303341
}
304-
this.capabilitiesFinished = true;
305-
this.emit("ready");
306-
return this.transport.reply<ICapabilitiesActionResponseData>(request, {
307-
capabilities: this.requestedCapabilities,
342+
343+
// See if we can expect a capabilities notification or not
344+
return this.getClientVersions().then(v => {
345+
if (v.includes(UnstableApiVersion.MSC2871)) {
346+
this.once(
347+
`action:${WidgetApiToWidgetAction.NotifyCapabilities}`,
348+
(ev: CustomEvent<INotifyCapabilitiesActionRequest>) => {
349+
this.approvedCapabilities = ev.detail.data.approved;
350+
this.emit("ready");
351+
},
352+
);
353+
} else {
354+
// if we can't expect notification, we're as done as we can be
355+
this.emit("ready");
356+
}
357+
358+
// in either case, reply to that capabilities request
359+
this.capabilitiesFinished = true;
360+
return this.transport.reply<ICapabilitiesActionResponseData>(request, {
361+
capabilities: this.requestedCapabilities,
362+
});
308363
});
309364
}
310365
}

src/interfaces/ApiVersion.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export enum MatrixApiVersion {
2222

2323
export enum UnstableApiVersion {
2424
MSC2762 = "org.matrix.msc2762",
25+
MSC2871 = "org.matrix.msc2871",
2526
}
2627

2728
export type ApiVersion = MatrixApiVersion | UnstableApiVersion | string;
@@ -31,4 +32,5 @@ export const CurrentApiVersions: ApiVersion[] = [
3132
MatrixApiVersion.Prerelease2,
3233
MatrixApiVersion.V010,
3334
UnstableApiVersion.MSC2762,
35+
UnstableApiVersion.MSC2871,
3436
];

src/interfaces/CapabilitiesAction.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { IWidgetApiRequest, IWidgetApiRequestEmptyData } from "./IWidgetApiRequest";
17+
import { IWidgetApiRequest, IWidgetApiRequestData, IWidgetApiRequestEmptyData } from "./IWidgetApiRequest";
1818
import { WidgetApiToWidgetAction } from "./WidgetApiAction";
1919
import { Capability } from "./Capabilities";
20-
import { IWidgetApiResponseData } from "./IWidgetApiResponse";
20+
import { IWidgetApiAcknowledgeResponseData, IWidgetApiResponseData } from "./IWidgetApiResponse";
2121

2222
export interface ICapabilitiesActionRequest extends IWidgetApiRequest {
2323
action: WidgetApiToWidgetAction.Capabilities;
@@ -31,3 +31,17 @@ export interface ICapabilitiesActionResponseData extends IWidgetApiResponseData
3131
export interface ICapabilitiesActionResponse extends ICapabilitiesActionRequest {
3232
response: ICapabilitiesActionResponseData;
3333
}
34+
35+
export interface INotifyCapabilitiesActionRequestData extends IWidgetApiRequestData {
36+
requested: Capability[];
37+
approved: Capability[];
38+
}
39+
40+
export interface INotifyCapabilitiesActionRequest extends IWidgetApiRequest {
41+
action: WidgetApiToWidgetAction.NotifyCapabilities;
42+
data: INotifyCapabilitiesActionRequestData;
43+
}
44+
45+
export interface INotifyCapabilitiesActionResponse extends INotifyCapabilitiesActionRequest {
46+
response: IWidgetApiAcknowledgeResponseData;
47+
}

src/interfaces/WidgetApiAction.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
export enum WidgetApiToWidgetAction {
1818
SupportedApiVersions = "supported_api_versions",
1919
Capabilities = "capabilities",
20+
NotifyCapabilities = "notify_capabilities",
2021
TakeScreenshot = "screenshot",
2122
UpdateVisibility = "visibility",
2223
OpenIDCredentials = "openid_credentials",

0 commit comments

Comments
 (0)