From 087113d8a1214ba4c7df03bfe66747d8d944280c Mon Sep 17 00:00:00 2001 From: Markus Hatvan Date: Tue, 14 Sep 2021 11:03:28 +0200 Subject: chore: convert JS to TS (#1934) --- .eslintrc | 31 ++++++++++--- package-lock.json | 6 +++ package.json | 1 + src/electron/Settings.js | 61 ------------------------- src/electron/Settings.ts | 66 +++++++++++++++++++++++++++ src/electron/deepLinking.js | 7 --- src/electron/deepLinking.ts | 7 +++ src/electron/exception.js | 4 -- src/electron/exception.ts | 4 ++ src/electron/ipc-api/appIndicator.js | 73 ------------------------------ src/electron/ipc-api/appIndicator.ts | 83 ++++++++++++++++++++++++++++++++++ src/electron/ipc-api/autoUpdate.js | 84 ---------------------------------- src/electron/ipc-api/autoUpdate.ts | 88 ++++++++++++++++++++++++++++++++++++ src/electron/ipc-api/cld.js | 20 -------- src/electron/ipc-api/cld.ts | 23 ++++++++++ src/electron/ipc-api/dnd.js | 23 ---------- src/electron/ipc-api/dnd.ts | 23 ++++++++++ src/electron/ipc-api/download.js | 51 --------------------- src/electron/ipc-api/download.ts | 59 ++++++++++++++++++++++++ src/electron/ipc-api/focusState.js | 9 ---- src/electron/ipc-api/focusState.ts | 11 +++++ src/electron/ipc-api/index.js | 19 -------- src/electron/ipc-api/index.ts | 24 ++++++++++ src/electron/ipc-api/localServer.js | 48 -------------------- src/electron/ipc-api/localServer.ts | 47 +++++++++++++++++++ src/electron/ipc-api/settings.js | 14 ------ src/electron/ipc-api/settings.ts | 14 ++++++ src/electron/macOSPermissions.js | 80 -------------------------------- src/electron/macOSPermissions.ts | 83 ++++++++++++++++++++++++++++++++++ src/electron/webview-ime-focus.js | 41 ----------------- src/electron/windowUtils.js | 11 ----- src/electron/windowUtils.ts | 13 ++++++ 32 files changed, 576 insertions(+), 552 deletions(-) delete mode 100644 src/electron/Settings.js create mode 100644 src/electron/Settings.ts delete mode 100644 src/electron/deepLinking.js create mode 100644 src/electron/deepLinking.ts delete mode 100644 src/electron/exception.js create mode 100644 src/electron/exception.ts delete mode 100644 src/electron/ipc-api/appIndicator.js create mode 100644 src/electron/ipc-api/appIndicator.ts delete mode 100644 src/electron/ipc-api/autoUpdate.js create mode 100644 src/electron/ipc-api/autoUpdate.ts delete mode 100644 src/electron/ipc-api/cld.js create mode 100644 src/electron/ipc-api/cld.ts delete mode 100644 src/electron/ipc-api/dnd.js create mode 100644 src/electron/ipc-api/dnd.ts delete mode 100644 src/electron/ipc-api/download.js create mode 100644 src/electron/ipc-api/download.ts delete mode 100644 src/electron/ipc-api/focusState.js create mode 100644 src/electron/ipc-api/focusState.ts delete mode 100644 src/electron/ipc-api/index.js create mode 100644 src/electron/ipc-api/index.ts delete mode 100644 src/electron/ipc-api/localServer.js create mode 100644 src/electron/ipc-api/localServer.ts delete mode 100644 src/electron/ipc-api/settings.js create mode 100644 src/electron/ipc-api/settings.ts delete mode 100644 src/electron/macOSPermissions.js create mode 100644 src/electron/macOSPermissions.ts delete mode 100644 src/electron/webview-ime-focus.js delete mode 100644 src/electron/windowUtils.js create mode 100644 src/electron/windowUtils.ts diff --git a/.eslintrc b/.eslintrc index 121f28eda..26159ed87 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,16 +2,23 @@ "root": true, "parser": "@babel/eslint-parser", "extends": "eslint-config-airbnb", - "plugins": ["jest"], + "plugins": [ + "jest" + ], "overrides": [ { - "files": ["**/*.ts", "**/*.tsx"], + "files": [ + "**/*.ts", + "**/*.tsx" + ], "env": { "browser": true, "es6": true, "node": true }, - "extends": ["airbnb-typescript"], + "extends": [ + "airbnb-typescript" + ], "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaFeatures": { @@ -21,13 +28,16 @@ "sourceType": "module", "project": "./tsconfig.json" }, - "plugins": ["@typescript-eslint"], + "plugins": [ + "@typescript-eslint" + ], "rules": { // eslint "arrow-parens": 0, "array-callback-return": 1, "class-methods-use-this": 0, "consistent-return": 0, + "function-paren-newline": 0, "implicit-arrow-linebreak": 0, "linebreak-style": 0, "max-len": 0, @@ -70,9 +80,13 @@ "jsx-a11y/label-has-for": [ 2, { - "components": ["Label"], + "components": [ + "Label" + ], "required": { - "every": ["id"] + "every": [ + "id" + ] }, "allowChildren": false } @@ -113,7 +127,10 @@ "no-console": [ 1, { - "allow": ["warn", "error"] + "allow": [ + "warn", + "error" + ] } ], "no-param-reassign": 1, diff --git a/package-lock.json b/package-lock.json index 2128f78e3..a278c76f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6805,6 +6805,12 @@ "integrity": "sha512-/BHF5HAx3em7/KkzVKm3LrsD6HZAXuXO1AJZQ3cRRBZj4oHZDviWPYu0aEplAqDFNHZPW6d3G7KN+ONcCCC7pw==", "dev": true }, + "@types/mime-types": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.1.tgz", + "integrity": "sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==", + "dev": true + }, "@types/minimatch": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", diff --git a/package.json b/package.json index 9b9382e3c..c85e73eb8 100644 --- a/package.json +++ b/package.json @@ -154,6 +154,7 @@ "@types/expect.js": "0.3.29", "@types/fs-extra": "9.0.12", "@types/lodash": "4.14.172", + "@types/mime-types": "2.1.1", "@types/mocha": "9.0.0", "@types/ms": "0.7.31", "@types/node": "14.17.6", diff --git a/src/electron/Settings.js b/src/electron/Settings.js deleted file mode 100644 index 3e11bb175..000000000 --- a/src/electron/Settings.js +++ /dev/null @@ -1,61 +0,0 @@ -import { observable, toJS } from 'mobx'; -import { pathExistsSync, outputJsonSync, readJsonSync } from 'fs-extra'; -import { userDataPath } from '../environment'; - -const debug = require('debug')('Ferdi:Settings'); - -export default class Settings { - type = ''; - - @observable store = {}; - - constructor(type, defaultState = {}) { - this.type = type; - this.store = defaultState; - this.defaultState = defaultState; - - if (!pathExistsSync(this.settingsFile)) { - this._writeFile(); - } else { - this._hydrate(); - } - } - - set(settings) { - this.store = this._merge(settings); - - this._writeFile(); - } - - get all() { - return this.store; - } - - get allSerialized() { - return toJS(this.store); - } - - get(key) { - return this.store[key]; - } - - _merge(settings) { - return Object.assign(this.defaultState, this.store, settings); - } - - _hydrate() { - this.store = this._merge(readJsonSync(this.settingsFile)); - debug('Hydrate store', this.type, this.allSerialized); - } - - _writeFile() { - outputJsonSync(this.settingsFile, this.store, { - spaces: 2, - }); - debug('Write settings file', this.type, this.allSerialized); - } - - get settingsFile() { - return userDataPath('config', `${this.type === 'app' ? 'settings' : this.type}.json`); - } -} diff --git a/src/electron/Settings.ts b/src/electron/Settings.ts new file mode 100644 index 000000000..a1344aa97 --- /dev/null +++ b/src/electron/Settings.ts @@ -0,0 +1,66 @@ +import { observable, toJS } from 'mobx'; +import { pathExistsSync, outputJsonSync, readJsonSync } from 'fs-extra'; +import { userDataPath } from '../environment'; + +const debug = require('debug')('Ferdi:Settings'); + +export default class Settings { + type = ''; + + defaultState: {}; + + @observable store = {}; + + constructor(type: string, defaultState = {}) { + this.type = type; + this.store = defaultState; + this.defaultState = defaultState; + + if (!pathExistsSync(this.settingsFile)) { + this._writeFile(); + } else { + this._hydrate(); + } + } + + set(settings) { + this.store = this._merge(settings); + + this._writeFile(); + } + + get all() { + return this.store; + } + + get allSerialized() { + return toJS(this.store); + } + + get(key: string | number) { + return this.store[key]; + } + + _merge(settings) { + return Object.assign(this.defaultState, this.store, settings); + } + + _hydrate() { + this.store = this._merge(readJsonSync(this.settingsFile)); + debug('Hydrate store', this.type, this.allSerialized); + } + + _writeFile() { + outputJsonSync(this.settingsFile, this.store, { + spaces: 2, + }); + debug('Write settings file', this.type, this.allSerialized); + } + + get settingsFile() { + return userDataPath( + 'config', + `${this.type === 'app' ? 'settings' : this.type}.json`, + ); + } +} diff --git a/src/electron/deepLinking.js b/src/electron/deepLinking.js deleted file mode 100644 index 70e5cfb6f..000000000 --- a/src/electron/deepLinking.js +++ /dev/null @@ -1,7 +0,0 @@ -export default function handleDeepLink(window, rawUrl) { - const url = rawUrl.replace('ferdi://', ''); - - if (!url) return; - - window.webContents.send('navigateFromDeepLink', { url }); -} diff --git a/src/electron/deepLinking.ts b/src/electron/deepLinking.ts new file mode 100644 index 000000000..70e5cfb6f --- /dev/null +++ b/src/electron/deepLinking.ts @@ -0,0 +1,7 @@ +export default function handleDeepLink(window, rawUrl) { + const url = rawUrl.replace('ferdi://', ''); + + if (!url) return; + + window.webContents.send('navigateFromDeepLink', { url }); +} diff --git a/src/electron/exception.js b/src/electron/exception.js deleted file mode 100644 index 0065e2604..000000000 --- a/src/electron/exception.js +++ /dev/null @@ -1,4 +0,0 @@ -process.on('uncaughtException', (err) => { - // handle the error safely - console.error(err); -}); diff --git a/src/electron/exception.ts b/src/electron/exception.ts new file mode 100644 index 000000000..0065e2604 --- /dev/null +++ b/src/electron/exception.ts @@ -0,0 +1,4 @@ +process.on('uncaughtException', (err) => { + // handle the error safely + console.error(err); +}); diff --git a/src/electron/ipc-api/appIndicator.js b/src/electron/ipc-api/appIndicator.js deleted file mode 100644 index c6c261d0f..000000000 --- a/src/electron/ipc-api/appIndicator.js +++ /dev/null @@ -1,73 +0,0 @@ -import { app, ipcMain } from 'electron'; -import { join } from 'path'; -import { autorun } from 'mobx'; -import { isMac, isWindows, isLinux } from '../../environment'; - -const INDICATOR_TASKBAR = 'taskbar'; -const FILE_EXTENSION = isWindows ? 'ico' : 'png'; - -let isTrayIconEnabled; - -function getAsset(type, asset) { - return join( - __dirname, '..', '..', 'assets', 'images', type, process.platform, `${asset}.${FILE_EXTENSION}`, - ); -} - -export default (params) => { - autorun(() => { - isTrayIconEnabled = params.settings.app.get('enableSystemTray'); - - if (!isTrayIconEnabled) { - params.trayIcon.hide(); - } else if (isTrayIconEnabled) { - params.trayIcon.show(); - } - }); - - ipcMain.on('updateAppIndicator', (event, args) => { - // Flash TaskBar for windows, bounce Dock on Mac - if (!app.mainWindow.isFocused()) { - if (params.settings.app.get('notifyTaskBarOnMessage')) { - if (isWindows) { - app.mainWindow.flashFrame(true); - app.mainWindow.once('focus', () => app.mainWindow.flashFrame(false)); - } else if (isMac) { - app.dock.bounce('informational'); - } - } - } - - // Update badge - if (isMac - && typeof (args.indicator) === 'string') { - app.dock.setBadge(args.indicator); - } - - if ((isMac || isLinux) - && typeof (args.indicator) === 'number' - ) { - app.badgeCount = args.indicator; - } - - if (isWindows) { - if (typeof args.indicator === 'number' - && args.indicator !== 0) { - params.mainWindow.setOverlayIcon( - getAsset('taskbar', `${INDICATOR_TASKBAR}-${(args.indicator >= 10 ? 10 : args.indicator)}`), - '', - ); - } else if (typeof args.indicator === 'string') { - params.mainWindow.setOverlayIcon( - getAsset('taskbar', `${INDICATOR_TASKBAR}-alert`), - '', - ); - } else { - params.mainWindow.setOverlayIcon(null, ''); - } - } - - // Update Tray - params.trayIcon.setIndicator(args.indicator); - }); -}; diff --git a/src/electron/ipc-api/appIndicator.ts b/src/electron/ipc-api/appIndicator.ts new file mode 100644 index 000000000..5b5f2bac7 --- /dev/null +++ b/src/electron/ipc-api/appIndicator.ts @@ -0,0 +1,83 @@ +import { app, ipcMain } from 'electron'; +import { join } from 'path'; +import { autorun } from 'mobx'; +import { isMac, isWindows, isLinux } from '../../environment'; + +const INDICATOR_TASKBAR = 'taskbar'; +const FILE_EXTENSION = isWindows ? 'ico' : 'png'; + +let isTrayIconEnabled: boolean; + +function getAsset(type: 'tray' | 'taskbar', asset: string) { + return join( + __dirname, + '..', + '..', + 'assets', + 'images', + type, + process.platform, + `${asset}.${FILE_EXTENSION}`, + ); +} + +export default params => { + autorun(() => { + isTrayIconEnabled = params.settings.app.get('enableSystemTray'); + + if (!isTrayIconEnabled) { + params.trayIcon.hide(); + } else if (isTrayIconEnabled) { + params.trayIcon.show(); + } + }); + + ipcMain.on('updateAppIndicator', (_event, args) => { + // Flash TaskBar for windows, bounce Dock on Mac + if (!(app as any).mainWindow.isFocused()) { + if (params.settings.app.get('notifyTaskBarOnMessage')) { + if (isWindows) { + (app as any).mainWindow.flashFrame(true); + (app as any).mainWindow.once('focus', () => + (app as any).mainWindow.flashFrame(false), + ); + } else if (isMac) { + app.dock.bounce('informational'); + } + } + } + + // Update badge + if (isMac && typeof args.indicator === 'string') { + app.dock.setBadge(args.indicator); + } + + if ((isMac || isLinux) && typeof args.indicator === 'number') { + app.badgeCount = args.indicator; + } + + if (isWindows) { + if (typeof args.indicator === 'number' && args.indicator !== 0) { + params.mainWindow.setOverlayIcon( + getAsset( + 'taskbar', + `${INDICATOR_TASKBAR}-${ + args.indicator >= 10 ? 10 : args.indicator + }`, + ), + '', + ); + } else if (typeof args.indicator === 'string') { + params.mainWindow.setOverlayIcon( + getAsset('taskbar', `${INDICATOR_TASKBAR}-alert`), + '', + ); + } else { + params.mainWindow.setOverlayIcon(null, ''); + } + } + + // Update Tray + params.trayIcon.setIndicator(args.indicator); + }); +}; diff --git a/src/electron/ipc-api/autoUpdate.js b/src/electron/ipc-api/autoUpdate.js deleted file mode 100644 index 255595b9e..000000000 --- a/src/electron/ipc-api/autoUpdate.js +++ /dev/null @@ -1,84 +0,0 @@ -import { app, ipcMain } from 'electron'; -import { autoUpdater } from 'electron-updater'; -import { GITHUB_NIGHTLIES_REPO_NAME, GITHUB_ORG_NAME } from '../../config'; -import { isMac, isWindows } from '../../environment'; - -const debug = require('debug')('Ferdi:ipcApi:autoUpdate'); - -export default (params) => { - const enableUpdate = Boolean(params.settings.app.get('automaticUpdates')); - - if (!enableUpdate) { - autoUpdater.autoInstallOnAppQuit = false; - autoUpdater.autoDownload = false; - } else if (isMac || isWindows || process.env.APPIMAGE) { - ipcMain.on('autoUpdate', (event, args) => { - if (enableUpdate) { - try { - autoUpdater.autoInstallOnAppQuit = false; - autoUpdater.allowPrerelease = Boolean(params.settings.app.get('beta')); - autoUpdater.channel = autoUpdater.allowPrerelease ? 'beta' : 'latest'; - - if (params.settings.app.get('nightly')) { - autoUpdater.allowPrerelease = Boolean(params.settings.app.get('nightly')); - autoUpdater.channel = 'alpha'; - autoUpdater.setFeedURL({ - provider: 'github', - owner: GITHUB_ORG_NAME, - repo: GITHUB_NIGHTLIES_REPO_NAME, - }); - } - - if (args.action === 'check') { - debug('checking for update'); - autoUpdater.checkForUpdates(); - } else if (args.action === 'install') { - debug('installing update'); - autoUpdater.quitAndInstall(); - // we need to send a quit event - setTimeout(() => { - app.quit(); - }, 20); - } - } catch (e) { - console.error(e); - event.sender.send('autoUpdate', { error: true }); - } - } - }); - - autoUpdater.on('update-not-available', () => { - debug('update-not-available'); - params.mainWindow.webContents.send('autoUpdate', { available: false }); - }); - - autoUpdater.on('update-available', (event) => { - debug('update-available'); - - if (enableUpdate) { - params.mainWindow.webContents.send('autoUpdate', { - version: event.version, - available: true, - }); - } - }); - - autoUpdater.on('download-progress', (progressObj) => { - let logMessage = `Download speed: ${progressObj.bytesPerSecond}`; - logMessage = `${logMessage} - Downloaded ${progressObj.percent}%`; - logMessage = `${logMessage} (${progressObj.transferred}/${progressObj.total})`; - - debug(logMessage); - }); - - autoUpdater.on('update-downloaded', () => { - debug('update-downloaded'); - params.mainWindow.webContents.send('autoUpdate', { downloaded: true }); - }); - - autoUpdater.on('error', () => { - debug('update-error'); - params.mainWindow.webContents.send('autoUpdate', { error: true }); - }); - } -}; diff --git a/src/electron/ipc-api/autoUpdate.ts b/src/electron/ipc-api/autoUpdate.ts new file mode 100644 index 000000000..70890539d --- /dev/null +++ b/src/electron/ipc-api/autoUpdate.ts @@ -0,0 +1,88 @@ +import { app, ipcMain, BrowserWindow } from 'electron'; +import { autoUpdater } from 'electron-updater'; +import { GITHUB_NIGHTLIES_REPO_NAME, GITHUB_ORG_NAME } from '../../config'; +import { isMac, isWindows } from '../../environment'; + +const debug = require('debug')('Ferdi:ipcApi:autoUpdate'); + +export default (params: { mainWindow: BrowserWindow; settings: any }) => { + const enableUpdate = Boolean(params.settings.app.get('automaticUpdates')); + + if (!enableUpdate) { + autoUpdater.autoInstallOnAppQuit = false; + autoUpdater.autoDownload = false; + } else if (isMac || isWindows || process.env.APPIMAGE) { + ipcMain.on('autoUpdate', (event, args) => { + if (enableUpdate) { + try { + autoUpdater.autoInstallOnAppQuit = false; + autoUpdater.allowPrerelease = Boolean( + params.settings.app.get('beta'), + ); + autoUpdater.channel = autoUpdater.allowPrerelease ? 'beta' : 'latest'; + + if (params.settings.app.get('nightly')) { + autoUpdater.allowPrerelease = Boolean( + params.settings.app.get('nightly'), + ); + autoUpdater.channel = 'alpha'; + autoUpdater.setFeedURL({ + provider: 'github', + owner: GITHUB_ORG_NAME, + repo: GITHUB_NIGHTLIES_REPO_NAME, + }); + } + + if (args.action === 'check') { + debug('checking for update'); + autoUpdater.checkForUpdates(); + } else if (args.action === 'install') { + debug('installing update'); + autoUpdater.quitAndInstall(); + // we need to send a quit event + setTimeout(() => { + app.quit(); + }, 20); + } + } catch (e) { + console.error(e); + event.sender.send('autoUpdate', { error: true }); + } + } + }); + + autoUpdater.on('update-not-available', () => { + debug('update-not-available'); + params.mainWindow.webContents.send('autoUpdate', { available: false }); + }); + + autoUpdater.on('update-available', event => { + debug('update-available'); + + if (enableUpdate) { + params.mainWindow.webContents.send('autoUpdate', { + version: event.version, + available: true, + }); + } + }); + + autoUpdater.on('download-progress', progressObj => { + let logMessage = `Download speed: ${progressObj.bytesPerSecond}`; + logMessage = `${logMessage} - Downloaded ${progressObj.percent}%`; + logMessage = `${logMessage} (${progressObj.transferred}/${progressObj.total})`; + + debug(logMessage); + }); + + autoUpdater.on('update-downloaded', () => { + debug('update-downloaded'); + params.mainWindow.webContents.send('autoUpdate', { downloaded: true }); + }); + + autoUpdater.on('error', () => { + debug('update-error'); + params.mainWindow.webContents.send('autoUpdate', { error: true }); + }); + } +}; diff --git a/src/electron/ipc-api/cld.js b/src/electron/ipc-api/cld.js deleted file mode 100644 index 73e320ad9..000000000 --- a/src/electron/ipc-api/cld.js +++ /dev/null @@ -1,20 +0,0 @@ -import { ipcMain } from 'electron'; -import cld from 'cld'; - -const debug = require('debug')('Ferdi:ipcApi:cld'); - -export default async () => { - ipcMain.handle('detect-language', async (event, { sample }) => { - try { - const result = await cld.detect(sample); - debug('Checking language', 'probability', result.languages); - if (result.reliable) { - debug('Language detected reliably, setting spellchecker language to', result.languages[0].code); - - return result.languages[0].code; - } - } catch (e) { - console.error(e); - } - }); -}; diff --git a/src/electron/ipc-api/cld.ts b/src/electron/ipc-api/cld.ts new file mode 100644 index 000000000..b907f3730 --- /dev/null +++ b/src/electron/ipc-api/cld.ts @@ -0,0 +1,23 @@ +import { ipcMain } from 'electron'; +import cld from 'cld'; + +const debug = require('debug')('Ferdi:ipcApi:cld'); + +export default async () => { + ipcMain.handle('detect-language', async (_event, { sample }) => { + try { + const result = await cld.detect(sample); + debug('Checking language', 'probability', result.languages); + if (result.reliable) { + debug( + 'Language detected reliably, setting spellchecker language to', + result.languages[0].code, + ); + + return result.languages[0].code; + } + } catch (e) { + console.error(e); + } + }); +}; diff --git a/src/electron/ipc-api/dnd.js b/src/electron/ipc-api/dnd.js deleted file mode 100644 index 6fb8999a3..000000000 --- a/src/electron/ipc-api/dnd.js +++ /dev/null @@ -1,23 +0,0 @@ -import { ipcMain } from 'electron'; -import { getDoNotDisturb } from 'macos-notification-state'; -import { isMac } from '../../environment'; - -const debug = require('debug')('Ferdi:ipcApi:dnd'); - -export default async () => { - ipcMain.handle('get-dnd', async () => { - if (!isMac) { - debug('Not on macOS, returning', false); - return false; - } - - try { - const isDND = getDoNotDisturb(); - debug('Fetching DND state, set to', isDND); - return isDND; - } catch (e) { - console.error(e); - return false; - } - }); -}; diff --git a/src/electron/ipc-api/dnd.ts b/src/electron/ipc-api/dnd.ts new file mode 100644 index 000000000..6fb8999a3 --- /dev/null +++ b/src/electron/ipc-api/dnd.ts @@ -0,0 +1,23 @@ +import { ipcMain } from 'electron'; +import { getDoNotDisturb } from 'macos-notification-state'; +import { isMac } from '../../environment'; + +const debug = require('debug')('Ferdi:ipcApi:dnd'); + +export default async () => { + ipcMain.handle('get-dnd', async () => { + if (!isMac) { + debug('Not on macOS, returning', false); + return false; + } + + try { + const isDND = getDoNotDisturb(); + debug('Fetching DND state, set to', isDND); + return isDND; + } catch (e) { + console.error(e); + return false; + } + }); +}; diff --git a/src/electron/ipc-api/download.js b/src/electron/ipc-api/download.js deleted file mode 100644 index ba261ba1e..000000000 --- a/src/electron/ipc-api/download.js +++ /dev/null @@ -1,51 +0,0 @@ -import { ipcMain, dialog, BrowserWindow } from 'electron'; -import { download } from 'electron-dl'; -import mime from 'mime-types'; -import { writeFileSync } from 'fs-extra'; - -const debug = require('debug')('Ferdi:ipcApi:download'); - -function decodeBase64Image(dataString) { - const matches = dataString.match(/^data:([A-Za-z-+/]+);base64,(.+)$/); - - if (matches.length !== 3) { - return new Error('Invalid input string'); - } - - return Buffer.from(matches[2], 'base64'); -} - -export default (params) => { - ipcMain.on('download-file', async (event, { url, content, fileOptions = {} }) => { - const win = BrowserWindow.getFocusedWindow(); - - try { - if (!content) { - const dl = await download(win, url, { - saveAs: true, - }); - debug('File saved to', dl.savePath); - } else { - const extension = mime.extension(fileOptions.mime); - const filename = `${fileOptions.name}.${extension}`; - - try { - const saveDialog = await dialog.showSaveDialog(params.mainWindow, { - defaultPath: filename, - }); - - if (saveDialog.canceled) return; - - const binaryImage = decodeBase64Image(content); - writeFileSync(saveDialog.filePath, binaryImage, 'binary'); - - debug('File blob saved to', saveDialog.filePath); - } catch (err) { - console.log(err); - } - } - } catch (e) { - console.error(e); - } - }); -}; diff --git a/src/electron/ipc-api/download.ts b/src/electron/ipc-api/download.ts new file mode 100644 index 000000000..822658f26 --- /dev/null +++ b/src/electron/ipc-api/download.ts @@ -0,0 +1,59 @@ +import { ipcMain, dialog, BrowserWindow } from 'electron'; +import { download } from 'electron-dl'; +import mime from 'mime-types'; +import { writeFileSync } from 'fs-extra'; +import { PathLike } from 'fs'; + +const debug = require('debug')('Ferdi:ipcApi:download'); + +function decodeBase64Image(dataString: string) { + const matches = dataString.match(/^data:([A-Za-z-+/]+);base64,(.+)$/); + + if (matches?.length !== 3) { + return new Error('Invalid input string'); + } + + return Buffer.from(matches[2], 'base64'); +} + +export default (params: { mainWindow: BrowserWindow }) => { + ipcMain.on( + 'download-file', + async (_event, { url, content, fileOptions = {} }) => { + const win = BrowserWindow.getFocusedWindow(); + + try { + if (!content) { + const dl = await download(win!, url, { + saveAs: true, + }); + debug('File saved to', dl.savePath); + } else { + const extension = mime.extension(fileOptions.mime); + const filename = `${fileOptions.name}.${extension}`; + + try { + const saveDialog = await dialog.showSaveDialog(params.mainWindow, { + defaultPath: filename, + }); + + if (saveDialog.canceled) return; + + const binaryImage = decodeBase64Image(content); + writeFileSync( + saveDialog.filePath as PathLike, + binaryImage as unknown as string, + 'binary', + ); + + debug('File blob saved to', saveDialog.filePath); + } catch (err) { + console.log(err); + } + } + } catch (e) { + console.error(e); + } + }, + ); +}; diff --git a/src/electron/ipc-api/focusState.js b/src/electron/ipc-api/focusState.js deleted file mode 100644 index 0b4a0d8f3..000000000 --- a/src/electron/ipc-api/focusState.js +++ /dev/null @@ -1,9 +0,0 @@ -export default (params) => { - params.mainWindow.on('focus', () => { - params.mainWindow.webContents.send('isWindowFocused', true); - }); - - params.mainWindow.on('blur', () => { - params.mainWindow.webContents.send('isWindowFocused', false); - }); -}; diff --git a/src/electron/ipc-api/focusState.ts b/src/electron/ipc-api/focusState.ts new file mode 100644 index 000000000..01aa1a971 --- /dev/null +++ b/src/electron/ipc-api/focusState.ts @@ -0,0 +1,11 @@ +import { BrowserWindow } from 'electron'; + +export default (params: { mainWindow: BrowserWindow }) => { + params.mainWindow.on('focus', () => { + params.mainWindow.webContents.send('isWindowFocused', true); + }); + + params.mainWindow.on('blur', () => { + params.mainWindow.webContents.send('isWindowFocused', false); + }); +}; diff --git a/src/electron/ipc-api/index.js b/src/electron/ipc-api/index.js deleted file mode 100644 index 5da1edc39..000000000 --- a/src/electron/ipc-api/index.js +++ /dev/null @@ -1,19 +0,0 @@ -import autoUpdate from './autoUpdate'; -import settings from './settings'; -import appIndicator from './appIndicator'; -import download from './download'; -import localServer from './localServer'; -import cld from './cld'; -import dnd from './dnd'; -import focusState from './focusState'; - -export default (params) => { - settings(params); - autoUpdate(params); - appIndicator(params); - download(params); - localServer(params); - cld(params); - dnd(); - focusState(params); -}; diff --git a/src/electron/ipc-api/index.ts b/src/electron/ipc-api/index.ts new file mode 100644 index 000000000..06c50be10 --- /dev/null +++ b/src/electron/ipc-api/index.ts @@ -0,0 +1,24 @@ +import { BrowserWindow, Tray } from 'electron'; +import autoUpdate from './autoUpdate'; +import settings from './settings'; +import appIndicator from './appIndicator'; +import download from './download'; +import localServer from './localServer'; +import cld from './cld'; +import dnd from './dnd'; +import focusState from './focusState'; + +export default (params: { + mainWindow: BrowserWindow; + settings: any; + tray: Tray; +}) => { + settings(params); + autoUpdate(params); + appIndicator(params); + download(params); + localServer(params); + cld(); + dnd(); + focusState(params); +}; diff --git a/src/electron/ipc-api/localServer.js b/src/electron/ipc-api/localServer.js deleted file mode 100644 index 591e70504..000000000 --- a/src/electron/ipc-api/localServer.js +++ /dev/null @@ -1,48 +0,0 @@ -import { ipcMain } from 'electron'; -import net from 'net'; -import { LOCAL_HOSTNAME, LOCAL_PORT } from '../../config'; -import { userDataPath } from '../../environment'; -import startServer from '../../internal-server/start'; - -const portInUse = function (port) { - return new Promise((resolve) => { - const server = net.createServer((socket) => { - socket.write('Echo server\r\n'); - socket.pipe(socket); - }); - - server.listen(port, LOCAL_HOSTNAME); - server.on('error', () => { - resolve(true); - }); - server.on('listening', () => { - server.close(); - resolve(false); - }); - }); -}; - -let localServerStarted = false; - -export default (params) => { - ipcMain.on('startLocalServer', () => { - if (!localServerStarted) { - // Find next unused port for server - let port = LOCAL_PORT; - (async () => { - // eslint-disable-next-line no-await-in-loop - while ((await portInUse(port)) && port < LOCAL_PORT + 10) { - port += 1; - } - console.log('Starting local server on port', port); - - startServer(userDataPath(), port); - - params.mainWindow.webContents.send('localServerPort', { - port, - }); - })(); - localServerStarted = true; - } - }); -}; diff --git a/src/electron/ipc-api/localServer.ts b/src/electron/ipc-api/localServer.ts new file mode 100644 index 000000000..d318b93a5 --- /dev/null +++ b/src/electron/ipc-api/localServer.ts @@ -0,0 +1,47 @@ +import { ipcMain, BrowserWindow } from 'electron'; +import net from 'net'; +import { LOCAL_HOSTNAME, LOCAL_PORT } from '../../config'; +import { userDataPath } from '../../environment'; +import startServer from '../../internal-server/start'; + +const portInUse = (port: number): Promise => + new Promise(resolve => { + const server = net.createServer(socket => { + socket.write('Echo server\r\n'); + socket.pipe(socket); + }); + + server.listen(port, LOCAL_HOSTNAME); + server.on('error', () => { + resolve(true); + }); + server.on('listening', () => { + server.close(); + resolve(false); + }); + }); + +let localServerStarted = false; + +export default (params: { mainWindow: BrowserWindow }) => { + ipcMain.on('startLocalServer', () => { + if (!localServerStarted) { + // Find next unused port for server + let port = LOCAL_PORT; + (async () => { + // eslint-disable-next-line no-await-in-loop + while ((await portInUse(port)) && port < LOCAL_PORT + 10) { + port += 1; + } + console.log('Starting local server on port', port); + + startServer(userDataPath(), port); + + params.mainWindow.webContents.send('localServerPort', { + port, + }); + })(); + localServerStarted = true; + } + }); +}; diff --git a/src/electron/ipc-api/settings.js b/src/electron/ipc-api/settings.js deleted file mode 100644 index 15182739c..000000000 --- a/src/electron/ipc-api/settings.js +++ /dev/null @@ -1,14 +0,0 @@ -import { ipcMain } from 'electron'; - -export default (params) => { - ipcMain.on('getAppSettings', (event, type) => { - params.mainWindow.webContents.send('appSettings', { - type, - data: params.settings[type].allSerialized, - }); - }); - - ipcMain.on('updateAppSettings', (event, args) => { - params.settings[args.type].set(args.data); - }); -}; diff --git a/src/electron/ipc-api/settings.ts b/src/electron/ipc-api/settings.ts new file mode 100644 index 000000000..72de6866d --- /dev/null +++ b/src/electron/ipc-api/settings.ts @@ -0,0 +1,14 @@ +import { ipcMain, BrowserWindow, Settings } from 'electron'; + +export default (params: { mainWindow: BrowserWindow; settings: Settings }) => { + ipcMain.on('getAppSettings', (_event, type) => { + params.mainWindow.webContents.send('appSettings', { + type, + data: params.settings[type].allSerialized, + }); + }); + + ipcMain.on('updateAppSettings', (_event, args) => { + params.settings[args.type].set(args.data); + }); +}; diff --git a/src/electron/macOSPermissions.js b/src/electron/macOSPermissions.js deleted file mode 100644 index 887af2903..000000000 --- a/src/electron/macOSPermissions.js +++ /dev/null @@ -1,80 +0,0 @@ -import { systemPreferences, dialog } from 'electron'; -import { pathExistsSync, mkdirSync, writeFileSync } from 'fs-extra'; -import macosVersion from 'macos-version'; -import { dirname } from 'path'; -import { askForScreenCaptureAccess } from 'node-mac-permissions'; -import { userDataPath } from '../environment'; - -const debug = require('debug')('Ferdi:macOSPermissions'); - -const isExplicitScreenCapturePermissionReqd = macosVersion.isGreaterThanOrEqualTo('10.15'); -debug(`Should check explicitly for screen-capture permissions: ${isExplicitScreenCapturePermissionReqd}`); - -const filePath = userDataPath('.has-app-requested-screen-capture-permissions'); - -function hasPromptedForScreenCapturePermission() { - if (!isExplicitScreenCapturePermissionReqd) { - return false; - } - - debug('Checking if status file exists'); - return filePath && pathExistsSync(filePath); -} - -function hasScreenCapturePermissionAlreadyBeenGranted() { - if (!isExplicitScreenCapturePermissionReqd) { - return true; - } - - const screenCaptureStatus = systemPreferences.getMediaAccessStatus('screen'); - debug(`screen-capture permissions status: ${screenCaptureStatus}`); - return screenCaptureStatus === 'granted'; -} - -function createStatusFile() { - try { - writeFileSync(filePath, ''); - } catch (error) { - if (error.code === 'ENOENT') { - mkdirSync(dirname(filePath)); - writeFileSync(filePath, ''); - } - - throw error; - } -} - -export const askFormacOSPermissions = async mainWindow => { - debug('Checking camera & microphone permissions'); - systemPreferences.askForMediaAccess('camera'); - systemPreferences.askForMediaAccess('microphone'); - - if (hasScreenCapturePermissionAlreadyBeenGranted()) { - debug('Already obtained screen-capture permissions - writing status file'); - createStatusFile(); - return; - } - - if (!hasPromptedForScreenCapturePermission()) { - debug('Checking screen capture permissions'); - - const { response } = await dialog.showMessageBox(mainWindow, { - type: 'info', - message: 'Enable Screen Sharing', - detail: - 'To enable screen sharing for some services, Ferdi needs the permission to record your screen.', - buttons: ['Allow screen sharing', 'No', 'Ask me later'], - defaultId: 0, - cancelId: 2, - }); - - if (response === 0) { - debug('Asking for access'); - askForScreenCaptureAccess(); - createStatusFile(); - } else if (response === 1) { - debug("Don't ask again"); - createStatusFile(); - } - } -}; diff --git a/src/electron/macOSPermissions.ts b/src/electron/macOSPermissions.ts new file mode 100644 index 000000000..f5a8c7cc4 --- /dev/null +++ b/src/electron/macOSPermissions.ts @@ -0,0 +1,83 @@ +import { systemPreferences, BrowserWindow, dialog } from 'electron'; +import { pathExistsSync, mkdirSync, writeFileSync } from 'fs-extra'; +import macosVersion from 'macos-version'; +import { dirname } from 'path'; +import { askForScreenCaptureAccess } from 'node-mac-permissions'; +import { userDataPath } from '../environment'; + +const debug = require('debug')('Ferdi:macOSPermissions'); + +const isExplicitScreenCapturePermissionReqd = + macosVersion.isGreaterThanOrEqualTo('10.15'); +debug( + `Should check explicitly for screen-capture permissions: ${isExplicitScreenCapturePermissionReqd}`, +); + +const filePath = userDataPath('.has-app-requested-screen-capture-permissions'); + +function hasPromptedForScreenCapturePermission(): boolean { + if (!isExplicitScreenCapturePermissionReqd) { + return false; + } + + debug('Checking if status file exists'); + return filePath && pathExistsSync(filePath); +} + +function hasScreenCapturePermissionAlreadyBeenGranted(): boolean { + if (!isExplicitScreenCapturePermissionReqd) { + return true; + } + + const screenCaptureStatus = systemPreferences.getMediaAccessStatus('screen'); + debug(`screen-capture permissions status: ${screenCaptureStatus}`); + return screenCaptureStatus === 'granted'; +} + +function createStatusFile() { + try { + writeFileSync(filePath, ''); + } catch (error) { + if ((error as any).code === 'ENOENT') { + mkdirSync(dirname(filePath)); + writeFileSync(filePath, ''); + } + + throw error; + } +} + +export const askFormacOSPermissions = async (mainWindow: BrowserWindow) => { + debug('Checking camera & microphone permissions'); + systemPreferences.askForMediaAccess('camera'); + systemPreferences.askForMediaAccess('microphone'); + + if (hasScreenCapturePermissionAlreadyBeenGranted()) { + debug('Already obtained screen-capture permissions - writing status file'); + createStatusFile(); + return; + } + + if (!hasPromptedForScreenCapturePermission()) { + debug('Checking screen capture permissions'); + + const { response } = await dialog.showMessageBox(mainWindow, { + type: 'info', + message: 'Enable Screen Sharing', + detail: + 'To enable screen sharing for some services, Ferdi needs the permission to record your screen.', + buttons: ['Allow screen sharing', 'No', 'Ask me later'], + defaultId: 0, + cancelId: 2, + }); + + if (response === 0) { + debug('Asking for access'); + askForScreenCaptureAccess(); + createStatusFile(); + } else if (response === 1) { + debug("Don't ask again"); + createStatusFile(); + } + } +}; diff --git a/src/electron/webview-ime-focus.js b/src/electron/webview-ime-focus.js deleted file mode 100644 index e187ee0b4..000000000 --- a/src/electron/webview-ime-focus.js +++ /dev/null @@ -1,41 +0,0 @@ -import { webContents } from '@electron/remote'; -import { releaseDocumentFocus } from './webview-ime-focus-helpers'; - -function giveWebviewDocumentFocus(element) { - releaseDocumentFocus(); - - window.requestAnimationFrame(() => { - element.send('claim-document-focus'); - }); -} - -function elementIsUnfocusedWebview(element) { - return element.tagName === 'WEBVIEW' && !webContents.fromId(element.getWebContentsId()).isFocused(); -} - -function webviewDidAutofocus(element) { - function didKeyDown() { - element.removeEventListener('keydown', didKeyDown, true); - giveWebviewDocumentFocus(element); - } - - element.addEventListener('keydown', didKeyDown, true); -} - -function handleAutofocus(element) { - element.addEventListener('ipc-message', (event) => { - if (event.channel === 'autofocus') { - element.focus(); - webviewDidAutofocus(element); - } - }); -} - -function didMouseDown(event) { - if (elementIsUnfocusedWebview(event.target)) { - giveWebviewDocumentFocus(event.target); - } -} - -document.addEventListener('mousedown', didMouseDown, true); -document.querySelectorAll('webview').forEach(handleAutofocus); diff --git a/src/electron/windowUtils.js b/src/electron/windowUtils.js deleted file mode 100644 index 23b946ac4..000000000 --- a/src/electron/windowUtils.js +++ /dev/null @@ -1,11 +0,0 @@ -/* eslint import/prefer-default-export: 0 */ - -import { screen } from 'electron'; - -export function isPositionValid(position) { - const displays = screen.getAllDisplays(); - const { x, y } = position; - return displays.some(({ - workArea, - }) => x >= workArea.x && x <= workArea.x + workArea.width && y >= workArea.y && y <= workArea.y + workArea.height); -} diff --git a/src/electron/windowUtils.ts b/src/electron/windowUtils.ts new file mode 100644 index 000000000..1db1ff246 --- /dev/null +++ b/src/electron/windowUtils.ts @@ -0,0 +1,13 @@ +import { screen } from 'electron'; + +export function isPositionValid(position: { x: number; y: number }) { + const displays = screen.getAllDisplays(); + const { x, y } = position; + return displays.some( + ({ workArea }) => + x >= workArea.x && + x <= workArea.x + workArea.width && + y >= workArea.y && + y <= workArea.y + workArea.height, + ); +} -- cgit v1.2.3-54-g00ecf