diff --git a/src/main/windows/callsWidgetWindow.test.js b/src/main/windows/callsWidgetWindow.test.js index af474770..678463c4 100644 --- a/src/main/windows/callsWidgetWindow.test.js +++ b/src/main/windows/callsWidgetWindow.test.js @@ -556,7 +556,6 @@ describe('main/windows/callsWidgetWindow', () => { describe('handleGetDesktopSources', () => { const callsWidgetWindow = new CallsWidgetWindow(); - callsWidgetWindow.options = {callID: 'callID'}; callsWidgetWindow.win = { webContents: { send: jest.fn(), @@ -618,6 +617,7 @@ describe('main/windows/callsWidgetWindow', () => { PermissionsManager.doPermissionRequest.mockReturnValue(Promise.resolve(true)); ViewManager.getViewByWebContentsId.mockImplementation((id) => [...views.values()].find((view) => view.webContentsId === id)); callsWidgetWindow.mainView = views.get('server-1_view-1'); + callsWidgetWindow.options = {callID: 'callID'}; }); afterEach(() => { @@ -625,6 +625,34 @@ describe('main/windows/callsWidgetWindow', () => { 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 () => { 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([]); - 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(views.get('server-1_view-1').sendToRenderer).toHaveBeenCalledWith('calls-error', 'screen-permissions', 'callID'); 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([ { id: 'screen0', @@ -671,7 +712,7 @@ describe('main/windows/callsWidgetWindow', () => { ]); 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(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); }); + 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 () => { const originalPlatform = process.platform; Object.defineProperty(process, 'platform', { @@ -696,7 +758,7 @@ describe('main/windows/callsWidgetWindow', () => { ]); 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(resetScreensharePermissionsMacOS).toHaveBeenCalledTimes(1); @@ -704,7 +766,7 @@ describe('main/windows/callsWidgetWindow', () => { 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'); - await callsWidgetWindow.handleGetDesktopSources({sender: {id: 1}}, null); + await expect(callsWidgetWindow.handleGetDesktopSources({sender: {id: 1}}, null)).rejects.toThrow('permissions denied'); expect(resetScreensharePermissionsMacOS).toHaveBeenCalledTimes(2); expect(openScreensharePermissionsSettingsMacOS).toHaveBeenCalledTimes(1); diff --git a/src/main/windows/callsWidgetWindow.ts b/src/main/windows/callsWidgetWindow.ts index 6fafec75..d2f5e6b4 100644 --- a/src/main/windows/callsWidgetWindow.ts +++ b/src/main/windows/callsWidgetWindow.ts @@ -378,15 +378,15 @@ export class CallsWidgetWindow { private handleGetDesktopSources = async (event: IpcMainInvokeEvent, opts: Electron.SourcesOptions) => { log.debug('handleGetDesktopSources', opts); - if (event.sender.id !== this.mainView?.webContentsId) { - log.warn('handleGetDesktopSources', 'Blocked on wrong webContentsId'); - return []; + // For Calls we make an extra check to ensure the event is coming from the expected window (main view). + // Otherwise we want to allow for other plugins to ask for screen sharing sources. + if (this.mainView && event.sender.id !== this.mainView.webContentsId) { + throw new Error('handleGetDesktopSources: blocked on wrong webContentsId'); } const view = ViewManager.getViewByWebContentsId(event.sender.id); if (!view) { - log.error('handleGetDesktopSources: view not found'); - return []; + throw new Error('handleGetDesktopSources: view not found'); } 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})) { - log.warn('screen share permissions disallowed', view.webContentsId, view.view.server.url.toString()); - return []; + throw new Error('permissions denied'); } const screenPermissionsErrArgs = ['screen-permissions', this.callID]; @@ -425,10 +424,7 @@ export class CallsWidgetWindow { } if (!hasScreenPermissions || !sources.length) { - log.info('missing screen permissions'); - view.sendToRenderer(CALLS_ERROR, ...screenPermissionsErrArgs); - this.win?.webContents.send(CALLS_ERROR, ...screenPermissionsErrArgs); - return []; + throw new Error('handleGetDesktopSources: permissions denied'); } const message = sources.map((source) => { @@ -441,12 +437,14 @@ export class CallsWidgetWindow { return message; }).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); - this.win?.webContents.send(CALLS_ERROR, ...screenPermissionsErrArgs); - - return []; + throw new Error(`handleGetDesktopSources: desktopCapturer.getSources failed: ${err}`); }); };