diff --git a/.eslintrc.js b/.eslintrc.js index 19ff4a63b..2e30bb36b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -117,6 +117,21 @@ const typescriptRules = { '@typescript-eslint/array-type': ['error', { default: 'generic' }], + 'no-restricted-imports': 'off', + '@typescript-eslint/no-restricted-imports': [ + 'error', + { + paths: [ + { + name: 'electron', + importNames: ['BrowserWindow'], + message: 'Please use createBrowserWindow', + allowTypeImports: true, + }, + ], + }, + ], + // Overrides recommended by typescript-eslint // https://github.com/typescript-eslint/typescript-eslint/releases/tag/v4.0.0 '@typescript-eslint/no-redeclare': 'error', diff --git a/app/main.ts b/app/main.ts index fd0feb768..be2a175b2 100644 --- a/app/main.ts +++ b/app/main.ts @@ -12,9 +12,9 @@ import normalizePath from 'normalize-path'; import fastGlob from 'fast-glob'; import PQueue from 'p-queue'; import { get, pick, isNumber, isBoolean, some, debounce, noop } from 'lodash'; +import type { BrowserWindow } from 'electron'; import { app, - BrowserWindow, clipboard, dialog, ipcMain as ipc, @@ -72,6 +72,7 @@ import type { MenuOptionsType } from './menu'; import { createTemplate } from './menu'; import { installFileHandler, installWebHandler } from './protocol_filter'; import * as OS from '../ts/OS'; +import { createBrowserWindow } from '../ts/util/createBrowserWindow'; import { isProduction } from '../ts/util/version'; import { isSgnlHref, @@ -530,7 +531,7 @@ async function createWindow() { ); // Create the browser window. - mainWindow = new BrowserWindow(windowOptions); + mainWindow = createBrowserWindow(windowOptions); if (settingsChannel) { settingsChannel.setMainWindow(mainWindow); } @@ -968,7 +969,7 @@ function showScreenShareWindow(sourceName: string) { y: 24, }; - screenShareWindow = new BrowserWindow(options); + screenShareWindow = createBrowserWindow(options); handleCommonWindowEvents(screenShareWindow); @@ -1014,7 +1015,7 @@ function showAbout() { }, }; - aboutWindow = new BrowserWindow(options); + aboutWindow = createBrowserWindow(options); handleCommonWindowEvents(aboutWindow); @@ -1057,7 +1058,7 @@ function showSettingsWindow() { }, }; - settingsWindow = new BrowserWindow(options); + settingsWindow = createBrowserWindow(options); handleCommonWindowEvents(settingsWindow); @@ -1128,7 +1129,7 @@ async function showStickerCreator() { }, }; - stickerCreatorWindow = new BrowserWindow(options); + stickerCreatorWindow = createBrowserWindow(options); setupSpellChecker(stickerCreatorWindow, getLocale().messages); handleCommonWindowEvents(stickerCreatorWindow); @@ -1188,7 +1189,7 @@ async function showDebugLogWindow() { parent: mainWindow, }; - debugLogWindow = new BrowserWindow(options); + debugLogWindow = createBrowserWindow(options); handleCommonWindowEvents(debugLogWindow); @@ -1245,7 +1246,7 @@ function showPermissionsPopupWindow(forCalling: boolean, forCamera: boolean) { parent: mainWindow, }; - permissionsPopupWindow = new BrowserWindow(options); + permissionsPopupWindow = createBrowserWindow(options); handleCommonWindowEvents(permissionsPopupWindow); @@ -1482,7 +1483,7 @@ app.on('ready', async () => { 'sql.initialize is taking more than three seconds; showing loading dialog' ); - loadingWindow = new BrowserWindow({ + loadingWindow = createBrowserWindow({ show: false, width: 300, height: 265, diff --git a/ts/test-node/.eslintrc.js b/ts/test-node/.eslintrc.js new file mode 100644 index 000000000..1ccb87d8a --- /dev/null +++ b/ts/test-node/.eslintrc.js @@ -0,0 +1,8 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +module.exports = { + rules: { + '@typescript-eslint/no-restricted-imports': 'off', + }, +}; diff --git a/ts/test-node/.gitignore b/ts/test-node/.gitignore new file mode 100644 index 000000000..09a8422ef --- /dev/null +++ b/ts/test-node/.gitignore @@ -0,0 +1 @@ +!.eslintrc.js diff --git a/ts/test-node/util/createBrowserWindow_test.ts b/ts/test-node/util/createBrowserWindow_test.ts new file mode 100644 index 000000000..3b7d99b3b --- /dev/null +++ b/ts/test-node/util/createBrowserWindow_test.ts @@ -0,0 +1,14 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { assert } from 'chai'; +import { BrowserWindow } from 'electron'; + +import { createBrowserWindow } from '../../util/createBrowserWindow'; + +describe('createBrowserWindow', () => { + it('returns a BrowserWindow', () => { + const result = createBrowserWindow({ show: false }); + assert.instanceOf(result, BrowserWindow); + }); +}); diff --git a/ts/util/createBrowserWindow.ts b/ts/util/createBrowserWindow.ts new file mode 100644 index 000000000..af7383cd4 --- /dev/null +++ b/ts/util/createBrowserWindow.ts @@ -0,0 +1,25 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +// This is the one place that *should* be able to import `BrowserWindow`. +// eslint-disable-next-line @typescript-eslint/no-restricted-imports +import { BrowserWindow } from 'electron'; +import type { BrowserWindowConstructorOptions } from 'electron'; + +const SPELL_CHECKER_DICTIONARY_DOWNLOAD_URL = `https://updates.signal.org/desktop/hunspell_dictionaries/${process.versions.electron}/`; + +/** + * A wrapper around `new BrowserWindow` that updates the spell checker download URL. This + * function should be used instead of `new BrowserWindow`. + */ +export function createBrowserWindow( + options: BrowserWindowConstructorOptions +): BrowserWindow { + const result = new BrowserWindow(options); + + result.webContents.session.setSpellCheckerDictionaryDownloadURL( + SPELL_CHECKER_DICTIONARY_DOWNLOAD_URL + ); + + return result; +}