diff --git a/.eslintrc.js b/.eslintrc.js index 6795f11..e15d9d5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -30,8 +30,9 @@ module.exports = { "prefer-promise-reject-errors": "off", "quotes": "off", - // We disable this while we're transitioning "@typescript-eslint/no-explicit-any": "off", + // We're okay with assertion errors when we ask for them + "@typescript-eslint/no-non-null-assertion": "off", }, }], }; diff --git a/src/ipc.ts b/src/ipc.ts index 717a701..f5e3f29 100644 --- a/src/ipc.ts +++ b/src/ipc.ts @@ -46,7 +46,7 @@ ipcMain.on('loudNotification', function(): void { } }); -let powerSaveBlockerId: number = null; +let powerSaveBlockerId: number | null = null; ipcMain.on('app_onAction', function(_ev: IpcMainEvent, payload) { switch (payload.action) { case 'call_state': { @@ -147,11 +147,11 @@ ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) { case 'getPickleKey': try { - ret = await keytar.getPassword("element.io", `${args[0]}|${args[1]}`); + ret = await keytar?.getPassword("element.io", `${args[0]}|${args[1]}`); // migrate from riot.im (remove once we think there will no longer be // logins from the time of riot.im) if (ret === null) { - ret = await keytar.getPassword("riot.im", `${args[0]}|${args[1]}`); + ret = await keytar?.getPassword("riot.im", `${args[0]}|${args[1]}`); } } catch (e) { // if an error is thrown (e.g. keytar can't connect to the keychain), @@ -163,7 +163,7 @@ ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) { case 'createPickleKey': try { const pickleKey = await randomArray(32); - await keytar.setPassword("element.io", `${args[0]}|${args[1]}`, pickleKey); + await keytar?.setPassword("element.io", `${args[0]}|${args[1]}`, pickleKey); ret = pickleKey; } catch (e) { ret = null; @@ -172,10 +172,10 @@ ipcMain.on('ipcCall', async function(_ev: IpcMainEvent, payload) { case 'destroyPickleKey': try { - await keytar.deletePassword("element.io", `${args[0]}|${args[1]}`); + await keytar?.deletePassword("element.io", `${args[0]}|${args[1]}`); // migrate from riot.im (remove once we think there will no longer be // logins from the time of riot.im) - await keytar.deletePassword("riot.im", `${args[0]}|${args[1]}`); + await keytar?.deletePassword("riot.im", `${args[0]}|${args[1]}`); } catch (e) {} break; case 'getDesktopCapturerSources': diff --git a/src/keytar.ts b/src/keytar.ts index 58d3436..c335774 100644 --- a/src/keytar.ts +++ b/src/keytar.ts @@ -21,7 +21,7 @@ try { // eslint-disable-next-line @typescript-eslint/no-var-requires keytar = require('keytar'); } catch (e) { - if (e.code === "MODULE_NOT_FOUND") { + if ((e).code === "MODULE_NOT_FOUND") { console.log("Keytar isn't installed; secure key storage is disabled."); } else { console.warn("Keytar unexpected error:", e); diff --git a/src/language-helper.ts b/src/language-helper.ts index 47fe431..29189fe 100644 --- a/src/language-helper.ts +++ b/src/language-helper.ts @@ -27,7 +27,7 @@ export function _td(text: string): string { type SubstitutionValue = number | string; interface IVariables { - [key: string]: SubstitutionValue; + [key: string]: SubstitutionValue | undefined; count?: number; } @@ -66,13 +66,13 @@ export function _t(text: string, variables: IVariables = {}): string { type Component = () => void; -type TypedStore = Store<{ locale?: string | string[] }>; +type TypedStore = Store<{ locale?: string[] }>; export class AppLocalization { private static readonly STORE_KEY = "locale"; private readonly store: TypedStore; - private readonly localizedComponents: Set; + private readonly localizedComponents?: Set; constructor({ store, components = [] }: { store: TypedStore, components: Component[] }) { counterpart.registerTranslations("en", this.fetchTranslationJson("en_EN")); @@ -86,7 +86,8 @@ export class AppLocalization { this.store = store; if (this.store.has(AppLocalization.STORE_KEY)) { const locales = this.store.get(AppLocalization.STORE_KEY); - this.setAppLocale(locales); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.setAppLocale(locales!); } this.resetLocalizedUI(); @@ -110,7 +111,7 @@ export class AppLocalization { return require(`./i18n/strings/${this.denormalize(locale)}.json`); } catch (e) { console.log(`Could not fetch translation json for locale: '${locale}'`, e); - return null; + return {}; } } @@ -138,7 +139,7 @@ export class AppLocalization { public resetLocalizedUI(): void { console.log("Resetting the UI components after locale change"); - this.localizedComponents.forEach(componentSetup => { + this.localizedComponents?.forEach(componentSetup => { if (typeof componentSetup === "function") { componentSetup(); } diff --git a/src/protocol.ts b/src/protocol.ts index 7e3bd8b..1c79184 100644 --- a/src/protocol.ts +++ b/src/protocol.ts @@ -86,7 +86,7 @@ export function getProfileFromDeeplink(args: string[]): string | undefined { if (deeplinkUrl && deeplinkUrl.includes(SEARCH_PARAM)) { const parsedUrl = new URL(deeplinkUrl); if (parsedUrl.protocol === PROTOCOL) { - const ssoID = parsedUrl.searchParams.get(SEARCH_PARAM); + const ssoID = parsedUrl.searchParams.get(SEARCH_PARAM)!; const store = readStore(); console.log("Forwarding to profile: ", store[ssoID]); return store[ssoID]; diff --git a/src/seshat.ts b/src/seshat.ts index 49d48d3..77022b9 100644 --- a/src/seshat.ts +++ b/src/seshat.ts @@ -40,7 +40,7 @@ try { ReindexError = seshatModule.ReindexError; seshatSupported = true; } catch (e) { - if (e.code === "MODULE_NOT_FOUND") { + if ((e).code === "MODULE_NOT_FOUND") { console.log("Seshat isn't installed, event indexing is disabled."); } else { console.warn("Seshat unexpected error:", e); @@ -49,7 +49,7 @@ try { const eventStorePath = path.join(app.getPath('userData'), 'EventStore'); -let eventIndex: SeshatType = null; +let eventIndex: SeshatType | null = null; const seshatDefaultPassphrase = "DEFAULT_PASSPHRASE"; async function getOrCreatePassphrase(key: string): Promise { @@ -66,9 +66,8 @@ async function getOrCreatePassphrase(key: string): Promise { } catch (e) { console.log("Error getting the event index passphrase out of the secret store", e); } - } else { - return seshatDefaultPassphrase; } + return seshatDefaultPassphrase; } const deleteContents = async (p: string): Promise => { @@ -180,7 +179,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise { case 'addEventToIndex': try { - eventIndex.addEvent(args[0], args[1]); + eventIndex?.addEvent(args[0], args[1]); } catch (e) { sendError(payload.id, e); return; @@ -189,7 +188,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise { case 'deleteEvent': try { - ret = await eventIndex.deleteEvent(args[0]); + ret = await eventIndex?.deleteEvent(args[0]); } catch (e) { sendError(payload.id, e); return; @@ -198,7 +197,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise { case 'commitLiveEvents': try { - ret = await eventIndex.commit(); + ret = await eventIndex?.commit(); } catch (e) { sendError(payload.id, e); return; @@ -207,7 +206,7 @@ ipcMain.on('seshat', async function(_ev: IpcMainEvent, payload): Promise { case 'searchEventIndex': try { - ret = await eventIndex.search(args[0]); + ret = await eventIndex?.search(args[0]); } catch (e) { sendError(payload.id, e); return; diff --git a/src/tray.ts b/src/tray.ts index 6d95d30..e015238 100644 --- a/src/tray.ts +++ b/src/tray.ts @@ -22,7 +22,7 @@ import fs from "fs"; import { _t } from "./language-helper"; -let trayIcon: Tray = null; +let trayIcon: Tray | null = null; export function hasTray(): boolean { return (trayIcon !== null); @@ -65,7 +65,7 @@ export function create(config: IConfig): void { if (!favicons || favicons.length <= 0 || !favicons[0].startsWith('data:')) { if (lastFavicon !== null) { global.mainWindow.setIcon(defaultIcon); - trayIcon.setImage(defaultIcon); + trayIcon?.setImage(defaultIcon); lastFavicon = null; } return; @@ -88,12 +88,12 @@ export function create(config: IConfig): void { } } - trayIcon.setImage(newFavicon); + trayIcon?.setImage(newFavicon); global.mainWindow.setIcon(newFavicon); }); global.mainWindow.webContents.on('page-title-updated', function(ev, title) { - trayIcon.setToolTip(title); + trayIcon?.setToolTip(title); }); } diff --git a/src/updater.ts b/src/updater.ts index bb17efb..77fa401 100644 --- a/src/updater.ts +++ b/src/updater.ts @@ -70,6 +70,7 @@ export function start(updateBaseUrl: string): void { // I'm not even going to try to guess which feed style they'd use if they // implemented it on Linux, or if it would be different again. console.log('Auto update not supported on this platform'); + return; } if (url) { diff --git a/src/webcontents-handler.ts b/src/webcontents-handler.ts index 2a9e2fd..b885431 100644 --- a/src/webcontents-handler.ts +++ b/src/webcontents-handler.ts @@ -50,7 +50,7 @@ function safeOpenURL(target: string): void { // (for instance, open /bin/sh does indeed open a terminal // with a shell, albeit with no arguments) const parsedUrl = url.parse(target); - if (PERMITTED_URL_SCHEMES.indexOf(parsedUrl.protocol) > -1) { + if (PERMITTED_URL_SCHEMES.includes(parsedUrl.protocol!)) { // explicitly use the URL re-assembled by the url library, // so we know the url parser has understood all the parts // of the input string @@ -69,7 +69,7 @@ function onWindowOrNavigate(ev: Event, target: string): void { } function writeNativeImage(filePath: string, img: NativeImage): Promise { - switch (filePath.split('.').pop().toLowerCase()) { + switch (filePath.split('.').pop()?.toLowerCase()) { case "jpg": case "jpeg": return fs.promises.writeFile(filePath, img.toJPEG(100)); @@ -181,7 +181,7 @@ function cutCopyPasteSelectContextMenus(params: ContextMenuParams): MenuItemCons options.push({ label: word, click: (menuItem, browserWindow) => { - browserWindow.webContents.replaceMisspelling(word); + browserWindow?.webContents.replaceMisspelling(word); }, }); }); @@ -190,7 +190,7 @@ function cutCopyPasteSelectContextMenus(params: ContextMenuParams): MenuItemCons }, { label: _t('Add to dictionary'), click: (menuItem, browserWindow) => { - browserWindow.webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord); + browserWindow?.webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord); }, }, { type: 'separator', @@ -251,8 +251,9 @@ function onEditableContextMenu(ev: Event, params: ContextMenuParams) { let userDownloadIndex = 0; const userDownloadMap = new Map(); // Map from id to path ipcMain.on('userDownloadAction', function(ev: IpcMainEvent, { id, open = false }) { - if (open) { - shell.openPath(userDownloadMap.get(id)); + const path = userDownloadMap.get(id); + if (open && path) { + shell.openPath(path); } userDownloadMap.delete(id); }); diff --git a/tsconfig.json b/tsconfig.json index 0733089..1c42f2e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,6 +16,7 @@ "dom" ], "types": ["jest", "node"], + "strict": true }, "include": [ "./src/**/*.ts",