[MM-50995] Harden Calls events handling (#2571)

* Simplify server switching logic in calls events

* Harden calls events handling

* Fix subpath
This commit is contained in:
Claudio Costa 2023-03-01 16:01:39 -06:00 committed by streamer45
parent ed5e92a09c
commit a92f2b7a06
No known key found for this signature in database
GPG key ID: C31222BC9269BBBC
6 changed files with 285 additions and 62 deletions

View file

@ -49,7 +49,7 @@ window.addEventListener('message', ({origin, data = {}} = {}) => {
case CALLS_POPOUT_FOCUS: case CALLS_POPOUT_FOCUS:
case CALLS_ERROR: case CALLS_ERROR:
case CALLS_LEAVE_CALL: { case CALLS_LEAVE_CALL: {
ipcRenderer.send(type, message); ipcRenderer.send(type, 'widget', message);
break; break;
} }
} }

View file

@ -30,13 +30,19 @@ jest.mock('../views/webContentEvents', () => ({
generateNewWindowListener: jest.fn(), generateNewWindowListener: jest.fn(),
})); }));
jest.mock('common/utils/url', () => {
const originalModule = jest.requireActual('common/utils/url');
return {
...originalModule,
...originalModule.default,
};
});
describe('main/windows/callsWidgetWindow', () => { describe('main/windows/callsWidgetWindow', () => {
describe('create CallsWidgetWindow', () => { describe('create CallsWidgetWindow', () => {
const widgetConfig = { const widgetConfig = {
callID: 'test-call-id', callID: 'test-call-id',
siteURL: 'http://localhost:8065',
title: '', title: '',
serverName: 'test-server-name',
channelURL: '/team/channel_id', channelURL: '/team/channel_id',
}; };
@ -48,8 +54,18 @@ describe('main/windows/callsWidgetWindow', () => {
view: { view: {
webContents: { webContents: {
send: jest.fn(), send: jest.fn(),
id: 'mainViewID',
}, },
}, },
serverInfo: {
server: {
name: 'test-server-name',
url: new URL('http://localhost:8065'),
},
},
getWebContents: () => ({
id: 'mainViewID',
}),
}; };
const baseWindow = new EventEmitter(); const baseWindow = new EventEmitter();
@ -170,6 +186,11 @@ describe('main/windows/callsWidgetWindow', () => {
}); });
it('resize', () => { it('resize', () => {
baseWindow.webContents = {
...baseWindow.webContents,
id: 'windowID',
};
baseWindow.show = jest.fn(() => { baseWindow.show = jest.fn(() => {
baseWindow.emit('show'); baseWindow.emit('show');
}); });
@ -202,7 +223,24 @@ describe('main/windows/callsWidgetWindow', () => {
height: MINIMUM_CALLS_WIDGET_HEIGHT, height: MINIMUM_CALLS_WIDGET_HEIGHT,
}); });
widgetWindow.onResize(null, { widgetWindow.onResize({
sender: {
id: 'badID',
},
},
'widget',
{
element: 'calls-widget',
width: 300,
height: 100,
});
expect(baseWindow.webContents.getZoomFactor).not.toHaveBeenCalled();
widgetWindow.onResize({
sender: baseWindow.webContents,
},
'widget',
{
element: 'calls-widget', element: 'calls-widget',
width: 300, width: 300,
height: 100, height: 100,
@ -217,6 +255,11 @@ describe('main/windows/callsWidgetWindow', () => {
}); });
it('zoom', () => { it('zoom', () => {
baseWindow.webContents = {
...baseWindow.webContents,
id: 'windowID',
};
baseWindow.show = jest.fn(() => { baseWindow.show = jest.fn(() => {
baseWindow.emit('show'); baseWindow.emit('show');
}); });
@ -244,7 +287,9 @@ describe('main/windows/callsWidgetWindow', () => {
expect(baseWindow.webContents.getZoomFactor).toHaveBeenCalledTimes(0); expect(baseWindow.webContents.getZoomFactor).toHaveBeenCalledTimes(0);
baseWindow.webContents.getZoomFactor = jest.fn(() => 2.0); baseWindow.webContents.getZoomFactor = jest.fn(() => 2.0);
widgetWindow.onResize(null, { widgetWindow.onResize({
sender: baseWindow.webContents,
}, 'widget', {
element: 'calls-widget', element: 'calls-widget',
width: 300, width: 300,
height: 100, height: 100,
@ -258,7 +303,10 @@ describe('main/windows/callsWidgetWindow', () => {
}); });
baseWindow.webContents.getZoomFactor = jest.fn(() => 0.5); baseWindow.webContents.getZoomFactor = jest.fn(() => 0.5);
widgetWindow.onResize(null, {
widgetWindow.onResize({
sender: baseWindow.webContents,
}, 'widget', {
element: 'calls-widget', element: 'calls-widget',
width: 300, width: 300,
height: 100, height: 100,
@ -290,11 +338,29 @@ describe('main/windows/callsWidgetWindow', () => {
it('getWidgetURL', () => { it('getWidgetURL', () => {
const config = { const config = {
...widgetConfig, ...widgetConfig,
siteURL: 'http://localhost:8065/subpath',
title: 'call test title #/&', title: 'call test title #/&',
}; };
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, config); const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, config);
const expected = `${config.siteURL}/plugins/${CALLS_PLUGIN_ID}/standalone/widget.html?call_id=${config.callID}&title=call+test+title+%23%2F%26`; const expected = `${mainView.serverInfo.server.url}plugins/${CALLS_PLUGIN_ID}/standalone/widget.html?call_id=${config.callID}&title=call+test+title+%23%2F%26`;
expect(widgetWindow.getWidgetURL()).toBe(expected);
});
it('getWidgetURL - under subpath', () => {
const config = {
...widgetConfig,
title: 'call test title #/&',
};
const view = {
serverInfo: {
server: {
url: new URL('http://localhost:8065/subpath'),
},
},
};
const widgetWindow = new CallsWidgetWindow(mainWindow, view, config);
const expected = `${view.serverInfo.server.url}/plugins/${CALLS_PLUGIN_ID}/standalone/widget.html?call_id=${config.callID}&title=call+test+title+%23%2F%26`;
expect(widgetWindow.getWidgetURL()).toBe(expected); expect(widgetWindow.getWidgetURL()).toBe(expected);
}); });
@ -302,6 +368,7 @@ describe('main/windows/callsWidgetWindow', () => {
baseWindow.webContents = { baseWindow.webContents = {
...baseWindow.webContents, ...baseWindow.webContents,
send: jest.fn(), send: jest.fn(),
id: 'windowID',
}; };
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig); const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
@ -309,7 +376,15 @@ describe('main/windows/callsWidgetWindow', () => {
sourceID: 'test-source-id', sourceID: 'test-source-id',
withAudio: false, withAudio: false,
}; };
widgetWindow.onShareScreen(null, '', message);
widgetWindow.onShareScreen({
sender: {id: 'badID'},
}, '', message);
expect(widgetWindow.win.webContents.send).not.toHaveBeenCalled();
widgetWindow.onShareScreen({
sender: baseWindow.webContents,
}, '', message);
expect(widgetWindow.win.webContents.send).toHaveBeenCalledWith(CALLS_WIDGET_SHARE_SCREEN, message); expect(widgetWindow.win.webContents.send).toHaveBeenCalledWith(CALLS_WIDGET_SHARE_SCREEN, message);
}); });
@ -317,13 +392,22 @@ describe('main/windows/callsWidgetWindow', () => {
baseWindow.webContents = { baseWindow.webContents = {
...baseWindow.webContents, ...baseWindow.webContents,
send: jest.fn(), send: jest.fn(),
id: 'windowID',
}; };
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig); const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
const message = { const message = {
callID: 'test-call-id', callID: 'test-call-id',
}; };
widgetWindow.onJoinedCall(null, message);
widgetWindow.onJoinedCall({
sender: {id: 'badID'},
}, message);
expect(widgetWindow.mainView.view.webContents.send).not.toHaveBeenCalled();
widgetWindow.onJoinedCall({
sender: baseWindow.webContents,
}, 'widget', message);
expect(widgetWindow.mainView.view.webContents.send).toHaveBeenCalledWith(CALLS_JOINED_CALL, message); expect(widgetWindow.mainView.view.webContents.send).toHaveBeenCalledWith(CALLS_JOINED_CALL, message);
}); });
@ -402,5 +486,27 @@ describe('main/windows/callsWidgetWindow', () => {
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig); const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
expect(widgetWindow.getMainView()).toEqual(mainView); expect(widgetWindow.getMainView()).toEqual(mainView);
}); });
it('isAllowedEvent', () => {
baseWindow.webContents = {
...baseWindow.webContents,
id: 'windowID',
};
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
expect(widgetWindow.isAllowedEvent({
sender: {
id: 'senderID',
},
})).toEqual(false);
expect(widgetWindow.isAllowedEvent({
sender: widgetWindow.mainView.view.webContents,
})).toEqual(true);
expect(widgetWindow.isAllowedEvent({
sender: baseWindow.webContents,
})).toEqual(true);
});
}); });
}); });

View file

@ -1,7 +1,6 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import url from 'url';
import {EventEmitter} from 'events'; import {EventEmitter} from 'events';
import {BrowserWindow, Rectangle, ipcMain, IpcMainEvent} from 'electron'; import {BrowserWindow, Rectangle, ipcMain, IpcMainEvent} from 'electron';
import log from 'electron-log'; import log from 'electron-log';
@ -23,7 +22,7 @@ import {
CALLS_PLUGIN_ID, CALLS_PLUGIN_ID,
} from 'common/utils/constants'; } from 'common/utils/constants';
import Utils from 'common/utils/util'; import Utils from 'common/utils/util';
import urlUtils from 'common/utils/url'; import urlUtils, {getFormattedPathName} from 'common/utils/url';
import { import {
CALLS_JOINED_CALL, CALLS_JOINED_CALL,
CALLS_POPOUT_FOCUS, CALLS_POPOUT_FOCUS,
@ -92,7 +91,7 @@ export default class CallsWidgetWindow extends EventEmitter {
} }
public getServerName() { public getServerName() {
return this.config.serverName; return this.mainView.serverInfo.server.name;
} }
public getChannelURL() { public getChannelURL() {
@ -121,8 +120,9 @@ export default class CallsWidgetWindow extends EventEmitter {
} }
private getWidgetURL() { private getWidgetURL() {
const u = new url.URL(this.config.siteURL); const u = urlUtils.parseURL(this.mainView.serverInfo.server.url.toString()) as URL;
u.pathname += `/plugins/${CALLS_PLUGIN_ID}/standalone/widget.html`; u.pathname = getFormattedPathName(u.pathname);
u.pathname += `plugins/${CALLS_PLUGIN_ID}/standalone/widget.html`;
u.searchParams.append('call_id', this.config.callID); u.searchParams.append('call_id', this.config.callID);
if (this.config.title) { if (this.config.title) {
u.searchParams.append('title', this.config.title); u.searchParams.append('title', this.config.title);
@ -131,9 +131,14 @@ export default class CallsWidgetWindow extends EventEmitter {
return u.toString(); return u.toString();
} }
private onResize = (event: IpcMainEvent, msg: CallsWidgetResizeMessage) => { private onResize = (ev: IpcMainEvent, _: string, msg: CallsWidgetResizeMessage) => {
log.debug('CallsWidgetWindow.onResize', msg); log.debug('CallsWidgetWindow.onResize', msg);
if (!this.isAllowedEvent(ev)) {
log.warn('CallsWidgetWindow.onResize', 'Disallowed calls event');
return;
}
const zoomFactor = this.win.webContents.getZoomFactor(); const zoomFactor = this.win.webContents.getZoomFactor();
const currBounds = this.win.getBounds(); const currBounds = this.win.getBounds();
const newBounds = { const newBounds = {
@ -146,11 +151,25 @@ export default class CallsWidgetWindow extends EventEmitter {
this.setBounds(newBounds); this.setBounds(newBounds);
} }
private onShareScreen = (ev: IpcMainEvent, viewName: string, message: CallsWidgetShareScreenMessage) => { private onShareScreen = (ev: IpcMainEvent, _: string, message: CallsWidgetShareScreenMessage) => {
log.debug('CallsWidgetWindow.onShareScreen');
if (!this.isAllowedEvent(ev)) {
log.warn('Disallowed calls event');
return;
}
this.win.webContents.send(CALLS_WIDGET_SHARE_SCREEN, message); this.win.webContents.send(CALLS_WIDGET_SHARE_SCREEN, message);
} }
private onJoinedCall = (ev: IpcMainEvent, message: CallsJoinedCallMessage) => { private onJoinedCall = (ev: IpcMainEvent, _: string, message: CallsJoinedCallMessage) => {
log.debug('CallsWidgetWindow.onJoinedCall');
if (!this.isAllowedEvent(ev)) {
log.warn('CallsWidgetWindow.onJoinedCall', 'Disallowed calls event');
return;
}
this.mainView.view.webContents.send(CALLS_JOINED_CALL, message); this.mainView.view.webContents.send(CALLS_JOINED_CALL, message);
} }
@ -229,5 +248,12 @@ export default class CallsWidgetWindow extends EventEmitter {
public getMainView() { public getMainView() {
return this.mainView; return this.mainView;
} }
public isAllowedEvent(event: IpcMainEvent) {
// Only allow events coming from either the widget window or the
// original Mattermost view that initiated it.
return event.sender.id === this.getWebContentsId() ||
event.sender.id === this.getMainView().getWebContents().id;
}
} }

View file

@ -1018,8 +1018,8 @@ describe('main/windows/windowManager', () => {
const view = { const view = {
name: 'server-1_tab-messaging', name: 'server-1_tab-messaging',
serverInfo: { serverInfo: {
remoteInfo: { server: {
siteURL: 'http://server-1.com', url: new URL('http://server-1.com'),
}, },
}, },
}; };
@ -1032,7 +1032,7 @@ describe('main/windows/windowManager', () => {
it('should create calls widget window', () => { it('should create calls widget window', () => {
expect(windowManager.callsWidgetWindow).toBeUndefined(); expect(windowManager.callsWidgetWindow).toBeUndefined();
windowManager.createCallsWidgetWindow(null, 'server-1_tab-messaging', {callID: 'test'}); windowManager.createCallsWidgetWindow('server-1_tab-messaging', {callID: 'test'});
expect(windowManager.callsWidgetWindow).toBeDefined(); expect(windowManager.callsWidgetWindow).toBeDefined();
}); });
@ -1040,7 +1040,7 @@ describe('main/windows/windowManager', () => {
const widgetWindow = windowManager.callsWidgetWindow; const widgetWindow = windowManager.callsWidgetWindow;
expect(widgetWindow).toBeDefined(); expect(widgetWindow).toBeDefined();
widgetWindow.getCallID = jest.fn(() => 'test'); widgetWindow.getCallID = jest.fn(() => 'test');
windowManager.createCallsWidgetWindow(null, 'server-1_tab-messaging', {callID: 'test'}); windowManager.createCallsWidgetWindow('server-1_tab-messaging', {callID: 'test'});
expect(windowManager.callsWidgetWindow).toEqual(widgetWindow); expect(windowManager.callsWidgetWindow).toEqual(widgetWindow);
}); });
@ -1048,7 +1048,7 @@ describe('main/windows/windowManager', () => {
const widgetWindow = windowManager.callsWidgetWindow; const widgetWindow = windowManager.callsWidgetWindow;
expect(widgetWindow).toBeDefined(); expect(widgetWindow).toBeDefined();
widgetWindow.getCallID = jest.fn(() => 'test'); widgetWindow.getCallID = jest.fn(() => 'test');
windowManager.createCallsWidgetWindow(null, 'server-1_tab-messaging', {callID: 'test2'}); windowManager.createCallsWidgetWindow('server-1_tab-messaging', {callID: 'test2'});
expect(windowManager.callsWidgetWindow).not.toEqual(widgetWindow); expect(windowManager.callsWidgetWindow).not.toEqual(widgetWindow);
}); });
}); });
@ -1061,12 +1061,18 @@ describe('main/windows/windowManager', () => {
}; };
beforeEach(() => { beforeEach(() => {
windowManager.callsWidgetWindow = new CallsWidgetWindow(); CallsWidgetWindow.mockImplementation(() => {
windowManager.callsWidgetWindow.win = { return {
isAllowedEvent: jest.fn().mockReturnValue(true),
win: {
webContents: { webContents: {
send: jest.fn(), send: jest.fn(),
}, },
},
}; };
});
windowManager.callsWidgetWindow = new CallsWidgetWindow();
Config.teams = [ Config.teams = [
{ {
@ -1140,7 +1146,7 @@ describe('main/windows/windowManager', () => {
}, },
]); ]);
await windowManager.handleGetDesktopSources(null, 'server-1_tab-1', null); await windowManager.handleGetDesktopSources('server-1_tab-1', null);
expect(windowManager.viewManager.views.get('server-1_tab-1').view.webContents.send).toHaveBeenCalledWith('desktop-sources-result', [ expect(windowManager.viewManager.views.get('server-1_tab-1').view.webContents.send).toHaveBeenCalledWith('desktop-sources-result', [
{ {
@ -1154,7 +1160,7 @@ describe('main/windows/windowManager', () => {
it('should send error with no sources', async () => { it('should send error with no sources', async () => {
jest.spyOn(desktopCapturer, 'getSources').mockResolvedValue([]); jest.spyOn(desktopCapturer, 'getSources').mockResolvedValue([]);
await windowManager.handleGetDesktopSources(null, 'server-2_tab-1', null); await windowManager.handleGetDesktopSources('server-2_tab-1', null);
expect(windowManager.callsWidgetWindow.win.webContents.send).toHaveBeenCalledWith('calls-error', { expect(windowManager.callsWidgetWindow.win.webContents.send).toHaveBeenCalledWith('calls-error', {
err: 'screen-permissions', err: 'screen-permissions',
}); });
@ -1175,7 +1181,7 @@ describe('main/windows/windowManager', () => {
]); ]);
jest.spyOn(systemPreferences, 'getMediaAccessStatus').mockReturnValue('denied'); jest.spyOn(systemPreferences, 'getMediaAccessStatus').mockReturnValue('denied');
await windowManager.handleGetDesktopSources(null, 'server-1_tab-1', null); await windowManager.handleGetDesktopSources('server-1_tab-1', null);
expect(systemPreferences.getMediaAccessStatus).toHaveBeenCalledWith('screen'); expect(systemPreferences.getMediaAccessStatus).toHaveBeenCalledWith('screen');
expect(windowManager.callsWidgetWindow.win.webContents.send).toHaveBeenCalledWith('calls-error', { expect(windowManager.callsWidgetWindow.win.webContents.send).toHaveBeenCalledWith('calls-error', {
@ -1204,7 +1210,7 @@ describe('main/windows/windowManager', () => {
]); ]);
jest.spyOn(systemPreferences, 'getMediaAccessStatus').mockReturnValue('denied'); jest.spyOn(systemPreferences, 'getMediaAccessStatus').mockReturnValue('denied');
await windowManager.handleGetDesktopSources(null, 'server-1_tab-1', null); await windowManager.handleGetDesktopSources('server-1_tab-1', null);
expect(windowManager.missingScreensharePermissions).toBe(true); expect(windowManager.missingScreensharePermissions).toBe(true);
expect(resetScreensharePermissionsMacOS).toHaveBeenCalledTimes(1); expect(resetScreensharePermissionsMacOS).toHaveBeenCalledTimes(1);
@ -1216,7 +1222,7 @@ describe('main/windows/windowManager', () => {
err: 'screen-permissions', err: 'screen-permissions',
}); });
await windowManager.handleGetDesktopSources(null, 'server-1_tab-1', null); await windowManager.handleGetDesktopSources('server-1_tab-1', null);
expect(resetScreensharePermissionsMacOS).toHaveBeenCalledTimes(2); expect(resetScreensharePermissionsMacOS).toHaveBeenCalledTimes(2);
expect(openScreensharePermissionsSettingsMacOS).toHaveBeenCalledTimes(1); expect(openScreensharePermissionsSettingsMacOS).toHaveBeenCalledTimes(1);
@ -1239,6 +1245,13 @@ describe('main/windows/windowManager', () => {
CallsWidgetWindow.mockImplementation(() => { CallsWidgetWindow.mockImplementation(() => {
return { return {
getServerName: () => 'server-1', getServerName: () => 'server-1',
getMainView: jest.fn().mockReturnValue({
view: {
webContents: {
send: jest.fn(),
},
},
}),
}; };
}); });
@ -1310,6 +1323,14 @@ describe('main/windows/windowManager', () => {
CallsWidgetWindow.mockImplementation(() => { CallsWidgetWindow.mockImplementation(() => {
return { return {
getServerName: () => 'server-2', getServerName: () => 'server-2',
getMainView: jest.fn().mockReturnValue({
view: {
webContents: {
send: jest.fn(),
},
},
}),
getChannelURL: jest.fn(),
}; };
}); });
@ -1398,7 +1419,7 @@ describe('main/windows/windowManager', () => {
it('should focus view and propagate error to main view', () => { it('should focus view and propagate error to main view', () => {
windowManager.callsWidgetWindow = new CallsWidgetWindow(); windowManager.callsWidgetWindow = new CallsWidgetWindow();
windowManager.handleCallsError(null, {err: 'client-error'}); windowManager.handleCallsError('', {err: 'client-error'});
expect(windowManager.switchServer).toHaveBeenCalledWith('server-2'); expect(windowManager.switchServer).toHaveBeenCalledWith('server-2');
expect(windowManager.mainWindow.focus).toHaveBeenCalled(); expect(windowManager.mainWindow.focus).toHaveBeenCalled();
expect(windowManager.callsWidgetWindow.getMainView().view.webContents.send).toHaveBeenCalledWith('calls-error', {err: 'client-error'}); expect(windowManager.callsWidgetWindow.getMainView().view.webContents.send).toHaveBeenCalledWith('calls-error', {err: 'client-error'});
@ -1407,6 +1428,7 @@ describe('main/windows/windowManager', () => {
describe('handleCallsLinkClick', () => { describe('handleCallsLinkClick', () => {
const windowManager = new WindowManager(); const windowManager = new WindowManager();
windowManager.switchServer = jest.fn();
const view1 = { const view1 = {
view: { view: {
webContents: { webContents: {
@ -1418,12 +1440,26 @@ describe('main/windows/windowManager', () => {
views: new Map([ views: new Map([
['server-1_tab-messaging', view1], ['server-1_tab-messaging', view1],
]), ]),
getCurrentView: jest.fn(),
}; };
beforeEach(() => {
CallsWidgetWindow.mockImplementation(() => {
return {
getServerName: () => 'server-1',
getMainView: jest.fn().mockReturnValue(view1),
};
});
});
afterEach(() => {
jest.resetAllMocks();
Config.teams = [];
});
it('should pass through the click link to browser history push', () => { it('should pass through the click link to browser history push', () => {
windowManager.viewManager.getCurrentView.mockReturnValue(view1); windowManager.callsWidgetWindow = new CallsWidgetWindow();
windowManager.handleCallsLinkClick(null, {link: '/other/subpath'}); windowManager.handleCallsLinkClick('', {link: '/other/subpath'});
expect(windowManager.switchServer).toHaveBeenCalledWith('server-1');
expect(view1.view.webContents.send).toBeCalledWith('browser-history-push', '/other/subpath'); expect(view1.view.webContents.send).toBeCalledWith('browser-history-push', '/other/subpath');
}); });
}); });
@ -1432,8 +1468,8 @@ describe('main/windows/windowManager', () => {
const view = { const view = {
name: 'server-1_tab-messaging', name: 'server-1_tab-messaging',
serverInfo: { serverInfo: {
remoteInfo: { server: {
siteURL: 'http://server-1.com', url: new URL('http://server-1.com'),
}, },
}, },
}; };
@ -1454,8 +1490,43 @@ describe('main/windows/windowManager', () => {
}; };
}); });
windowManager.createCallsWidgetWindow(null, 'server-1_tab-messaging', {callID: 'test'}); windowManager.createCallsWidgetWindow('server-1_tab-messaging', 'http://localhost:8065', {callID: 'test'});
expect(windowManager.getServerURLFromWebContentsId('callsID')).toBe(windowManager.callsWidgetWindow.getURL()); expect(windowManager.getServerURLFromWebContentsId('callsID')).toBe(windowManager.callsWidgetWindow.getURL());
}); });
}); });
describe('genCallsEventHandler', () => {
const windowManager = new WindowManager();
const handler = jest.fn();
it('should call handler if callsWidgetWindow is not defined', () => {
windowManager.genCallsEventHandler(handler)();
expect(handler).toHaveBeenCalledTimes(1);
});
it('should not call handler if source is not allowed', () => {
CallsWidgetWindow.mockImplementation(() => {
return {
isAllowedEvent: jest.fn().mockReturnValue(false),
};
});
windowManager.callsWidgetWindow = new CallsWidgetWindow();
windowManager.genCallsEventHandler(handler)();
expect(handler).not.toHaveBeenCalled();
});
it('should call handler if source is allowed', () => {
CallsWidgetWindow.mockImplementation(() => {
return {
isAllowedEvent: jest.fn().mockReturnValue(true),
};
});
windowManager.callsWidgetWindow = new CallsWidgetWindow();
windowManager.genCallsEventHandler(handler)();
expect(handler).toHaveBeenCalledTimes(1);
});
});
}); });

View file

@ -11,6 +11,7 @@ import {
CallsJoinCallMessage, CallsJoinCallMessage,
CallsErrorMessage, CallsErrorMessage,
CallsLinkClickMessage, CallsLinkClickMessage,
CallsEventHandler,
} from 'types/calls'; } from 'types/calls';
import { import {
@ -97,15 +98,17 @@ export class WindowManager {
ipcMain.on(APP_LOGGED_OUT, this.handleAppLoggedOut); ipcMain.on(APP_LOGGED_OUT, this.handleAppLoggedOut);
ipcMain.handle(GET_VIEW_NAME, this.handleGetViewName); ipcMain.handle(GET_VIEW_NAME, this.handleGetViewName);
ipcMain.handle(GET_VIEW_WEBCONTENTS_ID, this.handleGetWebContentsId); ipcMain.handle(GET_VIEW_WEBCONTENTS_ID, this.handleGetWebContentsId);
ipcMain.on(DISPATCH_GET_DESKTOP_SOURCES, this.handleGetDesktopSources);
ipcMain.on(RELOAD_CURRENT_VIEW, this.handleReloadCurrentView); ipcMain.on(RELOAD_CURRENT_VIEW, this.handleReloadCurrentView);
ipcMain.on(VIEW_FINISHED_RESIZING, this.handleViewFinishedResizing); ipcMain.on(VIEW_FINISHED_RESIZING, this.handleViewFinishedResizing);
ipcMain.on(CALLS_JOIN_CALL, this.createCallsWidgetWindow);
ipcMain.on(CALLS_LEAVE_CALL, () => this.callsWidgetWindow?.close()); // Calls handlers
ipcMain.on(DESKTOP_SOURCES_MODAL_REQUEST, this.handleDesktopSourcesModalRequest); ipcMain.on(DISPATCH_GET_DESKTOP_SOURCES, this.genCallsEventHandler(this.handleGetDesktopSources));
ipcMain.on(CALLS_WIDGET_CHANNEL_LINK_CLICK, this.handleCallsWidgetChannelLinkClick); ipcMain.on(DESKTOP_SOURCES_MODAL_REQUEST, this.genCallsEventHandler(this.handleDesktopSourcesModalRequest));
ipcMain.on(CALLS_ERROR, this.handleCallsError); ipcMain.on(CALLS_JOIN_CALL, this.genCallsEventHandler(this.createCallsWidgetWindow));
ipcMain.on(CALLS_LINK_CLICK, this.handleCallsLinkClick); ipcMain.on(CALLS_LEAVE_CALL, this.genCallsEventHandler(this.handleCallsLeave));
ipcMain.on(CALLS_WIDGET_CHANNEL_LINK_CLICK, this.genCallsEventHandler(this.handleCallsWidgetChannelLinkClick));
ipcMain.on(CALLS_ERROR, this.genCallsEventHandler(this.handleCallsError));
ipcMain.on(CALLS_LINK_CLICK, this.genCallsEventHandler(this.handleCallsLinkClick));
} }
handleUpdateConfig = () => { handleUpdateConfig = () => {
@ -114,7 +117,17 @@ export class WindowManager {
} }
} }
createCallsWidgetWindow = (event: IpcMainEvent, viewName: string, msg: CallsJoinCallMessage) => { genCallsEventHandler = (handler: CallsEventHandler) => {
return (event: IpcMainEvent, viewName: string, msg?: any) => {
if (this.callsWidgetWindow && !this.callsWidgetWindow.isAllowedEvent(event)) {
log.warn('WindowManager.genCallsEventHandler', 'Disallowed calls event');
return;
}
handler(viewName, msg);
};
}
createCallsWidgetWindow = (viewName: string, msg: CallsJoinCallMessage) => {
log.debug('WindowManager.createCallsWidgetWindow'); log.debug('WindowManager.createCallsWidgetWindow');
if (this.callsWidgetWindow) { if (this.callsWidgetWindow) {
// trying to join again the call we are already in should not be allowed. // trying to join again the call we are already in should not be allowed.
@ -130,10 +143,8 @@ export class WindowManager {
} }
this.callsWidgetWindow = new CallsWidgetWindow(this.mainWindow!, currentView, { this.callsWidgetWindow = new CallsWidgetWindow(this.mainWindow!, currentView, {
siteURL: currentView.serverInfo.remoteInfo.siteURL!,
callID: msg.callID, callID: msg.callID,
title: msg.title, title: msg.title,
serverName: this.currentServerName!,
channelURL: msg.channelURL, channelURL: msg.channelURL,
}); });
@ -146,23 +157,23 @@ export class WindowManager {
if (this.callsWidgetWindow) { if (this.callsWidgetWindow) {
this.switchServer(this.callsWidgetWindow.getServerName()); this.switchServer(this.callsWidgetWindow.getServerName());
this.mainWindow?.focus(); this.mainWindow?.focus();
const currentView = this.viewManager?.getCurrentView(); this.callsWidgetWindow.getMainView().view.webContents.send(DESKTOP_SOURCES_MODAL_REQUEST);
currentView?.view.webContents.send(DESKTOP_SOURCES_MODAL_REQUEST);
} }
} }
handleCallsWidgetChannelLinkClick = () => { handleCallsWidgetChannelLinkClick = () => {
log.debug('WindowManager.handleCallsWidgetChannelLinkClick'); log.debug('WindowManager.handleCallsWidgetChannelLinkClick');
if (this.callsWidgetWindow) { if (this.callsWidgetWindow) {
this.switchServer(this.callsWidgetWindow.getServerName()); this.switchServer(this.callsWidgetWindow.getServerName());
this.mainWindow?.focus(); this.mainWindow?.focus();
const currentView = this.viewManager?.getCurrentView(); this.callsWidgetWindow.getMainView().view.webContents.send(BROWSER_HISTORY_PUSH, this.callsWidgetWindow.getChannelURL());
currentView?.view.webContents.send(BROWSER_HISTORY_PUSH, this.callsWidgetWindow.getChannelURL());
} }
} }
handleCallsError = (event: IpcMainEvent, msg: CallsErrorMessage) => { handleCallsError = (_: string, msg: CallsErrorMessage) => {
log.debug('WindowManager.handleCallsError', msg); log.debug('WindowManager.handleCallsError', msg);
if (this.callsWidgetWindow) { if (this.callsWidgetWindow) {
this.switchServer(this.callsWidgetWindow.getServerName()); this.switchServer(this.callsWidgetWindow.getServerName());
this.mainWindow?.focus(); this.mainWindow?.focus();
@ -170,11 +181,20 @@ export class WindowManager {
} }
} }
handleCallsLinkClick = (_: IpcMainEvent, msg: CallsLinkClickMessage) => { handleCallsLinkClick = (_: string, msg: CallsLinkClickMessage) => {
log.debug('WindowManager.handleCallsLinkClick with linkURL', msg.link); log.debug('WindowManager.handleCallsLinkClick with linkURL', msg.link);
if (this.callsWidgetWindow) {
this.switchServer(this.callsWidgetWindow.getServerName());
this.mainWindow?.focus(); this.mainWindow?.focus();
const currentView = this.viewManager?.getCurrentView(); this.callsWidgetWindow.getMainView().view.webContents.send(BROWSER_HISTORY_PUSH, msg.link);
currentView?.view.webContents.send(BROWSER_HISTORY_PUSH, msg.link); }
}
handleCallsLeave = () => {
log.debug('WindowManager.handleCallsLeave');
this.callsWidgetWindow?.close();
} }
showSettingsWindow = () => { showSettingsWindow = () => {
@ -850,7 +870,7 @@ export class WindowManager {
return event.sender.id; return event.sender.id;
} }
handleGetDesktopSources = async (event: IpcMainEvent, viewName: string, opts: Electron.SourcesOptions) => { handleGetDesktopSources = async (viewName: string, opts: Electron.SourcesOptions) => {
log.debug('WindowManager.handleGetDesktopSources', {viewName, opts}); log.debug('WindowManager.handleGetDesktopSources', {viewName, opts});
const view = this.viewManager?.views.get(viewName); const view = this.viewManager?.views.get(viewName);

View file

@ -1,10 +1,8 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
export type CallsWidgetWindowConfig = { export type CallsWidgetWindowConfig = {
siteURL: string;
callID: string; callID: string;
title: string; title: string;
serverName: string;
channelURL: string; channelURL: string;
} }
@ -38,3 +36,5 @@ export type CallsErrorMessage = {
export type CallsLinkClickMessage = { export type CallsLinkClickMessage = {
link: string | URL; link: string | URL;
} }
export type CallsEventHandler = ((viewName: string, msg: any) => void) | ((viewName: string, opts: Electron.SourcesOptions) => Promise<void>);