[MM-60227] Allow plugins to ask for desktop sources for screen sharing (#3135)

* Allow plugins to ask for desktop sources for screen sharing

* Remove unnecessary logging in case of throwing errors

* Remove more redundant logging
This commit is contained in:
Claudio Costa 2024-09-18 08:49:56 -06:00 committed by GitHub
parent 42a0bc4759
commit d4e70db999
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 83 additions and 23 deletions

View file

@ -556,7 +556,6 @@ describe('main/windows/callsWidgetWindow', () => {
describe('handleGetDesktopSources', () => { describe('handleGetDesktopSources', () => {
const callsWidgetWindow = new CallsWidgetWindow(); const callsWidgetWindow = new CallsWidgetWindow();
callsWidgetWindow.options = {callID: 'callID'};
callsWidgetWindow.win = { callsWidgetWindow.win = {
webContents: { webContents: {
send: jest.fn(), send: jest.fn(),
@ -618,6 +617,7 @@ describe('main/windows/callsWidgetWindow', () => {
PermissionsManager.doPermissionRequest.mockReturnValue(Promise.resolve(true)); PermissionsManager.doPermissionRequest.mockReturnValue(Promise.resolve(true));
ViewManager.getViewByWebContentsId.mockImplementation((id) => [...views.values()].find((view) => view.webContentsId === id)); ViewManager.getViewByWebContentsId.mockImplementation((id) => [...views.values()].find((view) => view.webContentsId === id));
callsWidgetWindow.mainView = views.get('server-1_view-1'); callsWidgetWindow.mainView = views.get('server-1_view-1');
callsWidgetWindow.options = {callID: 'callID'};
}); });
afterEach(() => { afterEach(() => {
@ -625,6 +625,34 @@ describe('main/windows/callsWidgetWindow', () => {
callsWidgetWindow.missingScreensharePermissions = undefined; callsWidgetWindow.missingScreensharePermissions = undefined;
}); });
it('should send sources back - uninitialized', async () => {
callsWidgetWindow.mainView = undefined;
jest.spyOn(desktopCapturer, 'getSources').mockResolvedValue([
{
id: 'screen0',
thumbnail: {
toDataURL: jest.fn(),
},
},
{
id: 'window0',
thumbnail: {
toDataURL: jest.fn(),
},
},
]);
const sources = await callsWidgetWindow.handleGetDesktopSources({sender: {id: 1}}, null);
expect(sources).toEqual([
{
id: 'screen0',
},
{
id: 'window0',
},
]);
});
it('should send sources back', async () => { it('should send sources back', async () => {
jest.spyOn(desktopCapturer, 'getSources').mockResolvedValue([ jest.spyOn(desktopCapturer, 'getSources').mockResolvedValue([
{ {
@ -652,15 +680,28 @@ describe('main/windows/callsWidgetWindow', () => {
]); ]);
}); });
it('should send error with no sources', async () => { it('should throw and send error with no sources', async () => {
jest.spyOn(desktopCapturer, 'getSources').mockResolvedValue([]); jest.spyOn(desktopCapturer, 'getSources').mockResolvedValue([]);
await callsWidgetWindow.handleGetDesktopSources({sender: {id: 1}}, null);
await expect(callsWidgetWindow.handleGetDesktopSources({sender: {id: 1}}, null)).rejects.toThrow('permissions denied');
expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledWith('calls-error', 'screen-permissions', 'callID'); expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledWith('calls-error', 'screen-permissions', 'callID');
expect(views.get('server-1_view-1').sendToRenderer).toHaveBeenCalledWith('calls-error', 'screen-permissions', 'callID'); expect(views.get('server-1_view-1').sendToRenderer).toHaveBeenCalledWith('calls-error', 'screen-permissions', 'callID');
expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledTimes(1); expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledTimes(1);
}); });
it('should send error with no permissions', async () => { it('should throw but not send calls error when uninitialized', async () => {
callsWidgetWindow.options = undefined;
jest.spyOn(desktopCapturer, 'getSources').mockResolvedValue([]);
await expect(callsWidgetWindow.handleGetDesktopSources({sender: {id: 1}}, null)).rejects.toThrow('permissions denied');
expect(callsWidgetWindow.win.webContents.send).not.toHaveBeenCalled();
expect(views.get('server-1_view-1').sendToRenderer).not.toHaveBeenCalled();
});
it('should throw and send error with no permissions', async () => {
jest.spyOn(desktopCapturer, 'getSources').mockResolvedValue([ jest.spyOn(desktopCapturer, 'getSources').mockResolvedValue([
{ {
id: 'screen0', id: 'screen0',
@ -671,7 +712,7 @@ describe('main/windows/callsWidgetWindow', () => {
]); ]);
jest.spyOn(systemPreferences, 'getMediaAccessStatus').mockReturnValue('denied'); jest.spyOn(systemPreferences, 'getMediaAccessStatus').mockReturnValue('denied');
await callsWidgetWindow.handleGetDesktopSources({sender: {id: 1}}, null); await expect(callsWidgetWindow.handleGetDesktopSources({sender: {id: 1}}, null)).rejects.toThrow('permissions denied');
expect(systemPreferences.getMediaAccessStatus).toHaveBeenCalledWith('screen'); expect(systemPreferences.getMediaAccessStatus).toHaveBeenCalledWith('screen');
expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledWith('calls-error', 'screen-permissions', 'callID'); expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledWith('calls-error', 'screen-permissions', 'callID');
@ -680,6 +721,27 @@ describe('main/windows/callsWidgetWindow', () => {
expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledTimes(1); expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledTimes(1);
}); });
it('should throw but not send error with no permissions when uninitialized', async () => {
callsWidgetWindow.options = undefined;
jest.spyOn(desktopCapturer, 'getSources').mockResolvedValue([
{
id: 'screen0',
thumbnail: {
toDataURL: jest.fn(),
},
},
]);
jest.spyOn(systemPreferences, 'getMediaAccessStatus').mockReturnValue('denied');
await expect(callsWidgetWindow.handleGetDesktopSources({sender: {id: 1}}, null)).rejects.toThrow('permissions denied');
expect(systemPreferences.getMediaAccessStatus).toHaveBeenCalledWith('screen');
expect(callsWidgetWindow.win.webContents.send).not.toHaveBeenCalled();
expect(views.get('server-1_view-1').sendToRenderer).not.toHaveBeenCalled();
});
it('macos - no permissions', async () => { it('macos - no permissions', async () => {
const originalPlatform = process.platform; const originalPlatform = process.platform;
Object.defineProperty(process, 'platform', { Object.defineProperty(process, 'platform', {
@ -696,7 +758,7 @@ describe('main/windows/callsWidgetWindow', () => {
]); ]);
jest.spyOn(systemPreferences, 'getMediaAccessStatus').mockReturnValue('denied'); jest.spyOn(systemPreferences, 'getMediaAccessStatus').mockReturnValue('denied');
await callsWidgetWindow.handleGetDesktopSources({sender: {id: 1}}, null); await expect(callsWidgetWindow.handleGetDesktopSources({sender: {id: 1}}, null)).rejects.toThrow('permissions denied');
expect(callsWidgetWindow.missingScreensharePermissions).toBe(true); expect(callsWidgetWindow.missingScreensharePermissions).toBe(true);
expect(resetScreensharePermissionsMacOS).toHaveBeenCalledTimes(1); expect(resetScreensharePermissionsMacOS).toHaveBeenCalledTimes(1);
@ -704,7 +766,7 @@ describe('main/windows/callsWidgetWindow', () => {
expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledWith('calls-error', 'screen-permissions', 'callID'); expect(callsWidgetWindow.win.webContents.send).toHaveBeenCalledWith('calls-error', 'screen-permissions', 'callID');
expect(views.get('server-1_view-1').sendToRenderer).toHaveBeenCalledWith('calls-error', 'screen-permissions', 'callID'); expect(views.get('server-1_view-1').sendToRenderer).toHaveBeenCalledWith('calls-error', 'screen-permissions', 'callID');
await callsWidgetWindow.handleGetDesktopSources({sender: {id: 1}}, null); await expect(callsWidgetWindow.handleGetDesktopSources({sender: {id: 1}}, null)).rejects.toThrow('permissions denied');
expect(resetScreensharePermissionsMacOS).toHaveBeenCalledTimes(2); expect(resetScreensharePermissionsMacOS).toHaveBeenCalledTimes(2);
expect(openScreensharePermissionsSettingsMacOS).toHaveBeenCalledTimes(1); expect(openScreensharePermissionsSettingsMacOS).toHaveBeenCalledTimes(1);

View file

@ -378,15 +378,15 @@ export class CallsWidgetWindow {
private handleGetDesktopSources = async (event: IpcMainInvokeEvent, opts: Electron.SourcesOptions) => { private handleGetDesktopSources = async (event: IpcMainInvokeEvent, opts: Electron.SourcesOptions) => {
log.debug('handleGetDesktopSources', opts); log.debug('handleGetDesktopSources', opts);
if (event.sender.id !== this.mainView?.webContentsId) { // For Calls we make an extra check to ensure the event is coming from the expected window (main view).
log.warn('handleGetDesktopSources', 'Blocked on wrong webContentsId'); // Otherwise we want to allow for other plugins to ask for screen sharing sources.
return []; if (this.mainView && event.sender.id !== this.mainView.webContentsId) {
throw new Error('handleGetDesktopSources: blocked on wrong webContentsId');
} }
const view = ViewManager.getViewByWebContentsId(event.sender.id); const view = ViewManager.getViewByWebContentsId(event.sender.id);
if (!view) { if (!view) {
log.error('handleGetDesktopSources: view not found'); throw new Error('handleGetDesktopSources: view not found');
return [];
} }
if (process.platform === 'darwin' && systemPreferences.getMediaAccessStatus('screen') === 'denied') { if (process.platform === 'darwin' && systemPreferences.getMediaAccessStatus('screen') === 'denied') {
@ -407,8 +407,7 @@ export class CallsWidgetWindow {
} }
if (!await PermissionsManager.doPermissionRequest(view.webContentsId, 'screenShare', {requestingUrl: view.view.server.url.toString(), isMainFrame: false})) { if (!await PermissionsManager.doPermissionRequest(view.webContentsId, 'screenShare', {requestingUrl: view.view.server.url.toString(), isMainFrame: false})) {
log.warn('screen share permissions disallowed', view.webContentsId, view.view.server.url.toString()); throw new Error('permissions denied');
return [];
} }
const screenPermissionsErrArgs = ['screen-permissions', this.callID]; const screenPermissionsErrArgs = ['screen-permissions', this.callID];
@ -425,10 +424,7 @@ export class CallsWidgetWindow {
} }
if (!hasScreenPermissions || !sources.length) { if (!hasScreenPermissions || !sources.length) {
log.info('missing screen permissions'); throw new Error('handleGetDesktopSources: permissions denied');
view.sendToRenderer(CALLS_ERROR, ...screenPermissionsErrArgs);
this.win?.webContents.send(CALLS_ERROR, ...screenPermissionsErrArgs);
return [];
} }
const message = sources.map((source) => { const message = sources.map((source) => {
@ -441,12 +437,14 @@ export class CallsWidgetWindow {
return message; return message;
}).catch((err) => { }).catch((err) => {
log.error('desktopCapturer.getSources failed', err); // Only send calls error if this window has been initialized (i.e. we are in a call).
// The rest of the logic is shared so that other plugins can request screen sources.
if (this.callID) {
view.sendToRenderer(CALLS_ERROR, ...screenPermissionsErrArgs);
this.win?.webContents.send(CALLS_ERROR, ...screenPermissionsErrArgs);
}
view.sendToRenderer(CALLS_ERROR, ...screenPermissionsErrArgs); throw new Error(`handleGetDesktopSources: desktopCapturer.getSources failed: ${err}`);
this.win?.webContents.send(CALLS_ERROR, ...screenPermissionsErrArgs);
return [];
}); });
}; };