Fix embedded Element Call screen sharing (#9485)

* Fix embedded Element Call screen sharing

Makes it a request in each direction rather than a request and reply
since replies to requests time out and so can't wait for user interaction.

* Fix tests
This commit is contained in:
David Baker 2022-10-24 18:54:24 +01:00 committed by GitHub
parent e4c44dc282
commit 37e613bb05
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 50 additions and 20 deletions

View file

@ -816,7 +816,7 @@ export class ElementCall extends Call {
this.messaging!.on(`action:${ElementWidgetActions.HangupCall}`, this.onHangup); this.messaging!.on(`action:${ElementWidgetActions.HangupCall}`, this.onHangup);
this.messaging!.on(`action:${ElementWidgetActions.TileLayout}`, this.onTileLayout); this.messaging!.on(`action:${ElementWidgetActions.TileLayout}`, this.onTileLayout);
this.messaging!.on(`action:${ElementWidgetActions.SpotlightLayout}`, this.onSpotlightLayout); this.messaging!.on(`action:${ElementWidgetActions.SpotlightLayout}`, this.onSpotlightLayout);
this.messaging!.on(`action:${ElementWidgetActions.Screenshare}`, this.onScreenshare); this.messaging!.on(`action:${ElementWidgetActions.ScreenshareRequest}`, this.onScreenshareRequest);
} }
protected async performDisconnection(): Promise<void> { protected async performDisconnection(): Promise<void> {
@ -832,7 +832,7 @@ export class ElementCall extends Call {
this.messaging!.off(`action:${ElementWidgetActions.HangupCall}`, this.onHangup); this.messaging!.off(`action:${ElementWidgetActions.HangupCall}`, this.onHangup);
this.messaging!.off(`action:${ElementWidgetActions.TileLayout}`, this.onTileLayout); this.messaging!.off(`action:${ElementWidgetActions.TileLayout}`, this.onTileLayout);
this.messaging!.off(`action:${ElementWidgetActions.SpotlightLayout}`, this.onSpotlightLayout); this.messaging!.off(`action:${ElementWidgetActions.SpotlightLayout}`, this.onSpotlightLayout);
this.messaging!.off(`action:${ElementWidgetActions.Screenshare}`, this.onSpotlightLayout); this.messaging!.off(`action:${ElementWidgetActions.ScreenshareRequest}`, this.onScreenshareRequest);
super.setDisconnected(); super.setDisconnected();
} }
@ -952,19 +952,24 @@ export class ElementCall extends Call {
await this.messaging!.transport.reply(ev.detail, {}); // ack await this.messaging!.transport.reply(ev.detail, {}); // ack
}; };
private onScreenshare = async (ev: CustomEvent<IWidgetApiRequest>) => { private onScreenshareRequest = async (ev: CustomEvent<IWidgetApiRequest>) => {
ev.preventDefault(); ev.preventDefault();
if (PlatformPeg.get().supportsDesktopCapturer()) { if (PlatformPeg.get().supportsDesktopCapturer()) {
await this.messaging!.transport.reply(ev.detail, { pending: true });
const { finished } = Modal.createDialog(DesktopCapturerSourcePicker); const { finished } = Modal.createDialog(DesktopCapturerSourcePicker);
const [source] = await finished; const [source] = await finished;
await this.messaging!.transport.reply(ev.detail, { if (source) {
failed: !source, await this.messaging!.transport.send(ElementWidgetActions.ScreenshareStart, {
desktopCapturerSourceId: source, desktopCapturerSourceId: source,
}); });
} else { } else {
await this.messaging!.transport.reply(ev.detail, {}); await this.messaging!.transport.send(ElementWidgetActions.ScreenshareStop, {});
}
} else {
await this.messaging!.transport.reply(ev.detail, { pending: false });
} }
}; };
} }

View file

@ -26,10 +26,23 @@ export enum ElementWidgetActions {
MuteVideo = "io.element.mute_video", MuteVideo = "io.element.mute_video",
UnmuteVideo = "io.element.unmute_video", UnmuteVideo = "io.element.unmute_video",
StartLiveStream = "im.vector.start_live_stream", StartLiveStream = "im.vector.start_live_stream",
// Element Call -> host requesting to start a screenshare
// (ie. expects a ScreenshareStart once the user has picked a source)
// replies with { pending } where pending is true if the host has asked
// the user to choose a window and false if not (ie. if the host isn't
// running within Electron)
ScreenshareRequest = "io.element.screenshare_request",
// host -> Element Call telling EC to start screen sharing with
// the given source
ScreenshareStart = "io.element.screenshare_start",
// host -> Element Call telling EC to stop screen sharing, or that
// the user cancelled when selecting a source after a ScreenshareRequest
ScreenshareStop = "io.element.screenshare_stop",
// Actions for switching layouts // Actions for switching layouts
TileLayout = "io.element.tile_layout", TileLayout = "io.element.tile_layout",
SpotlightLayout = "io.element.spotlight_layout", SpotlightLayout = "io.element.spotlight_layout",
Screenshare = "io.element.screenshare",
OpenIntegrationManager = "integration_manager_open", OpenIntegrationManager = "integration_manager_open",

View file

@ -820,19 +820,25 @@ describe("ElementCall", () => {
await call.connect(); await call.connect();
messaging.emit( messaging.emit(
`action:${ElementWidgetActions.Screenshare}`, `action:${ElementWidgetActions.ScreenshareRequest}`,
new CustomEvent("widgetapirequest", { detail: {} }), new CustomEvent("widgetapirequest", { detail: {} }),
); );
waitFor(() => { await waitFor(() => {
expect(messaging!.transport.reply).toHaveBeenCalledWith( expect(messaging!.transport.reply).toHaveBeenCalledWith(
expect.objectContaining({}), expect.objectContaining({}),
expect.objectContaining({ desktopCapturerSourceId: sourceId }), expect.objectContaining({ pending: true }),
);
});
await waitFor(() => {
expect(messaging!.transport.send).toHaveBeenCalledWith(
"io.element.screenshare_start", expect.objectContaining({ desktopCapturerSourceId: sourceId }),
); );
}); });
}); });
it("passes failed if we couldn't get a source id", async () => { it("sends ScreenshareStop if we couldn't get a source id", async () => {
jest.spyOn(Modal, "createDialog").mockReturnValue( jest.spyOn(Modal, "createDialog").mockReturnValue(
{ finished: new Promise((r) => r([null])) } as IHandle<any[]>, { finished: new Promise((r) => r([null])) } as IHandle<any[]>,
); );
@ -841,32 +847,38 @@ describe("ElementCall", () => {
await call.connect(); await call.connect();
messaging.emit( messaging.emit(
`action:${ElementWidgetActions.Screenshare}`, `action:${ElementWidgetActions.ScreenshareRequest}`,
new CustomEvent("widgetapirequest", { detail: {} }), new CustomEvent("widgetapirequest", { detail: {} }),
); );
waitFor(() => { await waitFor(() => {
expect(messaging!.transport.reply).toHaveBeenCalledWith( expect(messaging!.transport.reply).toHaveBeenCalledWith(
expect.objectContaining({}), expect.objectContaining({}),
expect.objectContaining({ failed: true }), expect.objectContaining({ pending: true }),
);
});
await waitFor(() => {
expect(messaging!.transport.send).toHaveBeenCalledWith(
"io.element.screenshare_stop", expect.objectContaining({ }),
); );
}); });
}); });
it("passes an empty object if we don't support desktop capturer", async () => { it("replies with pending: false if we don't support desktop capturer", async () => {
jest.spyOn(PlatformPeg.get(), "supportsDesktopCapturer").mockReturnValue(false); jest.spyOn(PlatformPeg.get(), "supportsDesktopCapturer").mockReturnValue(false);
await call.connect(); await call.connect();
messaging.emit( messaging.emit(
`action:${ElementWidgetActions.Screenshare}`, `action:${ElementWidgetActions.ScreenshareRequest}`,
new CustomEvent("widgetapirequest", { detail: {} }), new CustomEvent("widgetapirequest", { detail: {} }),
); );
waitFor(() => { await waitFor(() => {
expect(messaging!.transport.reply).toHaveBeenCalledWith( expect(messaging!.transport.reply).toHaveBeenCalledWith(
expect.objectContaining({}), expect.objectContaining({}),
expect.objectContaining({}), expect.objectContaining({ pending: false }),
); );
}); });
}); });