diff --git a/src/main/app/app.test.js b/src/main/app/app.test.js index 35239096..c481fb79 100644 --- a/src/main/app/app.test.js +++ b/src/main/app/app.test.js @@ -36,6 +36,10 @@ jest.mock('main/certificateStore', () => ({ jest.mock('main/tray/tray', () => ({})); jest.mock('main/windows/windowManager', () => ({ getMainWindow: jest.fn(), + getViewNameByWebContentsId: jest.fn(), + viewManager: { + views: new Map(), + }, })); describe('main/app/app', () => { @@ -53,6 +57,7 @@ describe('main/app/app', () => { }); afterEach(() => { + WindowManager.viewManager.views.clear(); jest.resetAllMocks(); }); @@ -139,6 +144,16 @@ describe('main/app/app', () => { expect(CertificateStore.save).toHaveBeenCalled(); }); + it('should load URL using MattermostView when trusting certificate', async () => { + dialog.showMessageBox.mockResolvedValue({response: 0}); + const load = jest.fn(); + WindowManager.viewManager.views.set('view-name', {load}); + WindowManager.getViewNameByWebContentsId.mockReturnValue('view-name'); + await handleAppCertificateError(event, webContents, testURL, 'error-1', certificate, callback); + expect(callback).toHaveBeenCalledWith(true); + expect(load).toHaveBeenCalledWith(testURL); + }); + it('should explicitly untrust if user selects More Details and then cancel with the checkbox checked', async () => { dialog.showMessageBox.mockResolvedValueOnce({response: 0}).mockResolvedValueOnce({response: 1, checkboxChecked: true}); await handleAppCertificateError(event, webContents, testURL, 'error-1', certificate, callback); diff --git a/src/main/app/app.ts b/src/main/app/app.ts index 16b0dd17..90a13a67 100644 --- a/src/main/app/app.ts +++ b/src/main/app/app.ts @@ -136,7 +136,14 @@ export async function handleAppCertificateError(event: Event, webContents: WebCo CertificateStore.add(origin, certificate); CertificateStore.save(); certificateErrorCallbacks.get(errorID)(true); - webContents.loadURL(url); + + const viewName = WindowManager.getViewNameByWebContentsId(webContents.id); + if (viewName) { + const view = WindowManager.viewManager?.views.get(viewName); + view?.load(url); + } else { + webContents.loadURL(url); + } } else { if (result.checkboxChecked) { CertificateStore.add(origin, certificate, true); diff --git a/src/main/views/MattermostView.test.js b/src/main/views/MattermostView.test.js index 00251873..13973f55 100644 --- a/src/main/views/MattermostView.test.js +++ b/src/main/views/MattermostView.test.js @@ -97,6 +97,17 @@ describe('main/views/MattermostView', () => { expect(mattermostView.view.webContents.loadURL).toBeCalledWith('http://server-1.com/', expect.any(Object)); expect(mattermostView.loadRetry).toBeCalledWith('http://server-1.com/', error); }); + + it('should not retry when failing to load due to cert error', async () => { + const error = new Error('test'); + error.code = 'ERR_CERT_ERROR'; + const promise = Promise.reject(error); + mattermostView.view.webContents.loadURL.mockImplementation(() => promise); + mattermostView.load('a-bad { diff --git a/src/main/views/MattermostView.ts b/src/main/views/MattermostView.ts index c8e27b9a..ff2c6795 100644 --- a/src/main/views/MattermostView.ts +++ b/src/main/views/MattermostView.ts @@ -174,6 +174,13 @@ export class MattermostView extends EventEmitter { log.info(`[${Util.shorten(this.tab.name)}] Loading ${loadURL}`); const loading = this.view.webContents.loadURL(loadURL, {userAgent: composeUserAgent()}); loading.then(this.loadSuccess(loadURL)).catch((err) => { + if (err.code && err.code.startsWith('ERR_CERT')) { + WindowManager.sendToRenderer(LOAD_FAILED, this.tab.name, err.toString(), loadURL.toString()); + this.emit(LOAD_FAILED, this.tab.name, err.toString(), loadURL.toString()); + log.info(`[${Util.shorten(this.tab.name)}] Invalid certificate, stop retrying until the user decides what to do: ${err}.`); + this.status = Status.ERROR; + return; + } this.loadRetry(loadURL, err); }); }