diff options
Diffstat (limited to 'src/index.js')
-rw-r--r-- | src/index.js | 199 |
1 files changed, 138 insertions, 61 deletions
diff --git a/src/index.js b/src/index.js index 91004aac7..9146a23da 100644 --- a/src/index.js +++ b/src/index.js | |||
@@ -1,15 +1,9 @@ | |||
1 | /* eslint-disable import/first */ | 1 | /* eslint-disable import/first */ |
2 | 2 | ||
3 | import { | 3 | import { app, BrowserWindow, shell, ipcMain, session } from 'electron'; |
4 | app, | 4 | |
5 | BrowserWindow, | 5 | import { emptyDirSync, ensureFileSync } from 'fs-extra'; |
6 | shell, | 6 | import { join } from 'path'; |
7 | ipcMain, | ||
8 | session, | ||
9 | } from 'electron'; | ||
10 | |||
11 | import fs from 'fs-extra'; | ||
12 | import path from 'path'; | ||
13 | import windowStateKeeper from 'electron-window-state'; | 7 | import windowStateKeeper from 'electron-window-state'; |
14 | import { enforceMacOSAppLocation } from 'electron-util'; | 8 | import { enforceMacOSAppLocation } from 'electron-util'; |
15 | import ms from 'ms'; | 9 | import ms from 'ms'; |
@@ -25,6 +19,8 @@ import { | |||
25 | isWindows, | 19 | isWindows, |
26 | isLinux, | 20 | isLinux, |
27 | aboutAppDetails, | 21 | aboutAppDetails, |
22 | userDataRecipesPath, | ||
23 | userDataPath, | ||
28 | } from './environment'; | 24 | } from './environment'; |
29 | 25 | ||
30 | import { mainIpcHandler as basicAuthHandler } from './features/basicAuth'; | 26 | import { mainIpcHandler as basicAuthHandler } from './features/basicAuth'; |
@@ -69,8 +65,8 @@ function onDidLoad(fn) { | |||
69 | } | 65 | } |
70 | 66 | ||
71 | // Ensure that the recipe directory exists | 67 | // Ensure that the recipe directory exists |
72 | fs.emptyDirSync(path.join(app.getPath('userData'), 'recipes', 'temp')); | 68 | emptyDirSync(userDataRecipesPath('temp')); |
73 | fs.ensureFileSync(path.join(app.getPath('userData'), 'window-state.json')); | 69 | ensureFileSync(userDataPath('window-state.json')); |
74 | 70 | ||
75 | // Set App ID for Windows | 71 | // Set App ID for Windows |
76 | if (isWindows) { | 72 | if (isWindows) { |
@@ -90,7 +86,9 @@ if (settings.get('sentry')) { | |||
90 | const liftSingleInstanceLock = settings.get('liftSingleInstanceLock') || false; | 86 | const liftSingleInstanceLock = settings.get('liftSingleInstanceLock') || false; |
91 | 87 | ||
92 | // Force single window | 88 | // Force single window |
93 | const gotTheLock = liftSingleInstanceLock ? true : app.requestSingleInstanceLock(); | 89 | const gotTheLock = liftSingleInstanceLock |
90 | ? true | ||
91 | : app.requestSingleInstanceLock(); | ||
94 | if (!gotTheLock) { | 92 | if (!gotTheLock) { |
95 | app.quit(); | 93 | app.quit(); |
96 | } else { | 94 | } else { |
@@ -106,7 +104,7 @@ if (!gotTheLock) { | |||
106 | mainWindow.focus(); | 104 | mainWindow.focus(); |
107 | 105 | ||
108 | if (isWindows) { | 106 | if (isWindows) { |
109 | onDidLoad((window) => { | 107 | onDidLoad(window => { |
110 | // Keep only command line / deep linked arguments | 108 | // Keep only command line / deep linked arguments |
111 | const url = argv.slice(1); | 109 | const url = argv.slice(1); |
112 | if (url) { | 110 | if (url) { |
@@ -117,8 +115,14 @@ if (!gotTheLock) { | |||
117 | // Needs to be delayed to not interfere with mainWindow.restore(); | 115 | // Needs to be delayed to not interfere with mainWindow.restore(); |
118 | setTimeout(() => { | 116 | setTimeout(() => { |
119 | debug('Resetting windows via Task'); | 117 | debug('Resetting windows via Task'); |
120 | window.setPosition(DEFAULT_WINDOW_OPTIONS.x + 100, DEFAULT_WINDOW_OPTIONS.y + 100); | 118 | window.setPosition( |
121 | window.setSize(DEFAULT_WINDOW_OPTIONS.width, DEFAULT_WINDOW_OPTIONS.height); | 119 | DEFAULT_WINDOW_OPTIONS.x + 100, |
120 | DEFAULT_WINDOW_OPTIONS.y + 100, | ||
121 | ); | ||
122 | window.setSize( | ||
123 | DEFAULT_WINDOW_OPTIONS.width, | ||
124 | DEFAULT_WINDOW_OPTIONS.height, | ||
125 | ); | ||
122 | }, 1); | 126 | }, 1); |
123 | } else if (argv.includes('--quit')) { | 127 | } else if (argv.includes('--quit')) { |
124 | // Needs to be delayed to not interfere with mainWindow.restore(); | 128 | // Needs to be delayed to not interfere with mainWindow.restore(); |
@@ -135,7 +139,10 @@ if (!gotTheLock) { | |||
135 | 139 | ||
136 | // Fix Unity indicator issue | 140 | // Fix Unity indicator issue |
137 | // https://github.com/electron/electron/issues/9046 | 141 | // https://github.com/electron/electron/issues/9046 |
138 | if (isLinux && ['Pantheon', 'Unity:Unity7'].indexOf(process.env.XDG_CURRENT_DESKTOP) !== -1) { | 142 | if ( |
143 | isLinux && | ||
144 | ['Pantheon', 'Unity:Unity7'].indexOf(process.env.XDG_CURRENT_DESKTOP) !== -1 | ||
145 | ) { | ||
139 | process.env.XDG_CURRENT_DESKTOP = 'Unity'; | 146 | process.env.XDG_CURRENT_DESKTOP = 'Unity'; |
140 | } | 147 | } |
141 | 148 | ||
@@ -169,7 +176,9 @@ const createWindow = () => { | |||
169 | } | 176 | } |
170 | 177 | ||
171 | // Create the browser window. | 178 | // Create the browser window. |
172 | const backgroundColor = settings.get('darkMode') ? '#1E1E1E' : settings.get('accentColor'); | 179 | const backgroundColor = settings.get('darkMode') |
180 | ? '#1E1E1E' | ||
181 | : settings.get('accentColor'); | ||
173 | 182 | ||
174 | mainWindow = new BrowserWindow({ | 183 | mainWindow = new BrowserWindow({ |
175 | x: posX, | 184 | x: posX, |
@@ -181,19 +190,20 @@ const createWindow = () => { | |||
181 | show: false, | 190 | show: false, |
182 | titleBarStyle: isMac ? 'hidden' : '', | 191 | titleBarStyle: isMac ? 'hidden' : '', |
183 | frame: isLinux, | 192 | frame: isLinux, |
193 | spellcheck: settings.get('enableSpellchecking'), | ||
184 | backgroundColor, | 194 | backgroundColor, |
185 | webPreferences: { | 195 | webPreferences: { |
186 | nodeIntegration: true, | 196 | nodeIntegration: true, |
187 | contextIsolation: false, | 197 | contextIsolation: false, |
188 | webviewTag: true, | 198 | webviewTag: true, |
189 | preload: path.join(__dirname, 'sentry.js'), | 199 | preload: join(__dirname, 'sentry.js'), |
190 | enableRemoteModule: true, | 200 | enableRemoteModule: true, |
191 | }, | 201 | }, |
192 | }); | 202 | }); |
193 | 203 | ||
194 | app.on('web-contents-created', (e, contents) => { | 204 | app.on('web-contents-created', (e, contents) => { |
195 | if (contents.getType() === 'webview') { | 205 | if (contents.getType() === 'webview') { |
196 | contents.on('new-window', (event) => { | 206 | contents.on('new-window', event => { |
197 | event.preventDefault(); | 207 | event.preventDefault(); |
198 | }); | 208 | }); |
199 | } | 209 | } |
@@ -242,7 +252,7 @@ const createWindow = () => { | |||
242 | 252 | ||
243 | // Windows deep linking handling on app launch | 253 | // Windows deep linking handling on app launch |
244 | if (isWindows) { | 254 | if (isWindows) { |
245 | onDidLoad((window) => { | 255 | onDidLoad(window => { |
246 | const url = process.argv.slice(1); | 256 | const url = process.argv.slice(1); |
247 | if (url) { | 257 | if (url) { |
248 | handleDeepLink(window, url.toString()); | 258 | handleDeepLink(window, url.toString()); |
@@ -251,12 +261,16 @@ const createWindow = () => { | |||
251 | } | 261 | } |
252 | 262 | ||
253 | // Emitted when the window is closed. | 263 | // Emitted when the window is closed. |
254 | mainWindow.on('close', (e) => { | 264 | mainWindow.on('close', e => { |
255 | debug('Window: close window'); | 265 | debug('Window: close window'); |
256 | // Dereference the window object, usually you would store windows | 266 | // Dereference the window object, usually you would store windows |
257 | // in an array if your app supports multi windows, this is the time | 267 | // in an array if your app supports multi windows, this is the time |
258 | // when you should delete the corresponding element. | 268 | // when you should delete the corresponding element. |
259 | if (!willQuitApp && (settings.get('runInBackground') === undefined || settings.get('runInBackground'))) { | 269 | if ( |
270 | !willQuitApp && | ||
271 | (settings.get('runInBackground') === undefined || | ||
272 | settings.get('runInBackground')) | ||
273 | ) { | ||
260 | e.preventDefault(); | 274 | e.preventDefault(); |
261 | if (isWindows) { | 275 | if (isWindows) { |
262 | debug('Window: minimize'); | 276 | debug('Window: minimize'); |
@@ -315,7 +329,7 @@ const createWindow = () => { | |||
315 | 329 | ||
316 | if (isMac) { | 330 | if (isMac) { |
317 | // eslint-disable-next-line global-require | 331 | // eslint-disable-next-line global-require |
318 | const { default: askFormacOSPermissions } = require('./electron/macOSPermissions'); | 332 | const { askFormacOSPermissions } = require('./electron/macOSPermissions'); |
319 | setTimeout(() => askFormacOSPermissions(mainWindow), ms('30s')); | 333 | setTimeout(() => askFormacOSPermissions(mainWindow), ms('30s')); |
320 | } | 334 | } |
321 | 335 | ||
@@ -351,15 +365,24 @@ const createWindow = () => { | |||
351 | const argv = require('minimist')(process.argv.slice(1)); | 365 | const argv = require('minimist')(process.argv.slice(1)); |
352 | 366 | ||
353 | if (argv['auth-server-whitelist']) { | 367 | if (argv['auth-server-whitelist']) { |
354 | app.commandLine.appendSwitch('auth-server-whitelist', argv['auth-server-whitelist']); | 368 | app.commandLine.appendSwitch( |
369 | 'auth-server-whitelist', | ||
370 | argv['auth-server-whitelist'], | ||
371 | ); | ||
355 | } | 372 | } |
356 | if (argv['auth-negotiate-delegate-whitelist']) { | 373 | if (argv['auth-negotiate-delegate-whitelist']) { |
357 | app.commandLine.appendSwitch('auth-negotiate-delegate-whitelist', argv['auth-negotiate-delegate-whitelist']); | 374 | app.commandLine.appendSwitch( |
375 | 'auth-negotiate-delegate-whitelist', | ||
376 | argv['auth-negotiate-delegate-whitelist'], | ||
377 | ); | ||
358 | } | 378 | } |
359 | 379 | ||
360 | // Disable Chromium's poor MPRIS implementation | 380 | // Disable Chromium's poor MPRIS implementation |
361 | // and apply workaround for https://github.com/electron/electron/pull/26432 | 381 | // and apply workaround for https://github.com/electron/electron/pull/26432 |
362 | app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling,MediaSessionService,CrossOriginOpenerPolicy'); | 382 | app.commandLine.appendSwitch( |
383 | 'disable-features', | ||
384 | 'HardwareMediaKeyHandling,MediaSessionService,CrossOriginOpenerPolicy', | ||
385 | ); | ||
363 | 386 | ||
364 | // This method will be called when Electron has finished | 387 | // This method will be called when Electron has finished |
365 | // initialization and is ready to create browser windows. | 388 | // initialization and is ready to create browser windows. |
@@ -369,26 +392,36 @@ app.on('ready', () => { | |||
369 | enforceMacOSAppLocation(); | 392 | enforceMacOSAppLocation(); |
370 | 393 | ||
371 | // Register App URL | 394 | // Register App URL |
372 | app.setAsDefaultProtocolClient('ferdi'); | ||
373 | |||
374 | if (isDevMode) { | 395 | if (isDevMode) { |
375 | app.setAsDefaultProtocolClient('ferdi-dev'); | 396 | app.setAsDefaultProtocolClient('ferdi-dev'); |
397 | } else { | ||
398 | app.setAsDefaultProtocolClient('ferdi'); | ||
376 | } | 399 | } |
377 | 400 | ||
378 | if (isWindows) { | 401 | if (isWindows) { |
379 | app.setUserTasks([{ | 402 | app.setUserTasks([ |
380 | program: process.execPath, | 403 | { |
381 | arguments: `${isDevMode ? `${__dirname} ` : ''}--reset-window`, | 404 | program: process.execPath, |
382 | iconPath: asarPath(path.join(isDevMode ? `${__dirname}../src/` : __dirname, 'assets/images/taskbar/win32/display.ico')), | 405 | arguments: `${isDevMode ? `${__dirname} ` : ''}--reset-window`, |
383 | iconIndex: 0, | 406 | iconPath: asarPath( |
384 | title: 'Move Ferdi to Current Display', | 407 | join( |
385 | description: 'Restore the position and size of Ferdi', | 408 | isDevMode ? `${__dirname}../src/` : __dirname, |
386 | }, { | 409 | 'assets/images/taskbar/win32/display.ico', |
387 | program: process.execPath, | 410 | ), |
388 | arguments: `${isDevMode ? `${__dirname} ` : ''}--quit`, | 411 | ), |
389 | iconIndex: 0, | 412 | iconIndex: 0, |
390 | title: 'Quit Ferdi', | 413 | title: 'Move Ferdi to Current Display', |
391 | }]); | 414 | description: 'Restore the position and size of Ferdi', |
415 | }, | ||
416 | { | ||
417 | program: process.execPath, | ||
418 | arguments: `${isDevMode ? `${__dirname} ` : ''}--quit`, | ||
419 | iconIndex: 0, | ||
420 | iconPath: null, | ||
421 | title: 'Quit Ferdi', | ||
422 | description: null, | ||
423 | }, | ||
424 | ]); | ||
392 | } | 425 | } |
393 | 426 | ||
394 | createWindow(); | 427 | createWindow(); |
@@ -420,27 +453,35 @@ ipcMain.on('feature-basic-auth-credentials', (e, { user, password }) => { | |||
420 | 453 | ||
421 | ipcMain.on('open-browser-window', (e, { url, serviceId }) => { | 454 | ipcMain.on('open-browser-window', (e, { url, serviceId }) => { |
422 | const serviceSession = session.fromPartition(`persist:service-${serviceId}`); | 455 | const serviceSession = session.fromPartition(`persist:service-${serviceId}`); |
423 | const child = new BrowserWindow({ parent: mainWindow, webPreferences: { session: serviceSession } }); | 456 | const child = new BrowserWindow({ |
457 | parent: mainWindow, | ||
458 | webPreferences: { session: serviceSession }, | ||
459 | }); | ||
424 | child.show(); | 460 | child.show(); |
425 | child.loadURL(url); | 461 | child.loadURL(url); |
426 | debug('Received open-browser-window', url); | 462 | debug('Received open-browser-window', url); |
427 | }); | 463 | }); |
428 | 464 | ||
429 | ipcMain.on('modifyRequestHeaders', (e, { modifiedRequestHeaders, serviceId }) => { | 465 | ipcMain.on( |
430 | debug('Received modifyRequestHeaders', modifiedRequestHeaders, serviceId); | 466 | 'modifyRequestHeaders', |
431 | modifiedRequestHeaders.forEach((headerFilterSet) => { | 467 | (e, { modifiedRequestHeaders, serviceId }) => { |
432 | const { headers, requestFilters } = headerFilterSet; | 468 | debug('Received modifyRequestHeaders', modifiedRequestHeaders, serviceId); |
433 | session.fromPartition(`persist:service-${serviceId}`).webRequest.onBeforeSendHeaders(requestFilters, (details, callback) => { | 469 | modifiedRequestHeaders.forEach(headerFilterSet => { |
434 | for (const key in headers) { | 470 | const { headers, requestFilters } = headerFilterSet; |
435 | if (Object.prototype.hasOwnProperty.call(headers, key)) { | 471 | session |
436 | const value = headers[key]; | 472 | .fromPartition(`persist:service-${serviceId}`) |
437 | details.requestHeaders[key] = value; | 473 | .webRequest.onBeforeSendHeaders(requestFilters, (details, callback) => { |
438 | } | 474 | for (const key in headers) { |
439 | } | 475 | if (Object.prototype.hasOwnProperty.call(headers, key)) { |
440 | callback({ requestHeaders: details.requestHeaders }); | 476 | const value = headers[key]; |
477 | details.requestHeaders[key] = value; | ||
478 | } | ||
479 | } | ||
480 | callback({ requestHeaders: details.requestHeaders }); | ||
481 | }); | ||
441 | }); | 482 | }); |
442 | }); | 483 | }, |
443 | }); | 484 | ); |
444 | 485 | ||
445 | ipcMain.on('feature-basic-auth-cancel', () => { | 486 | ipcMain.on('feature-basic-auth-cancel', () => { |
446 | debug('Cancel basic auth'); | 487 | debug('Cancel basic auth'); |
@@ -449,16 +490,52 @@ ipcMain.on('feature-basic-auth-cancel', () => { | |||
449 | authCallback = noop; | 490 | authCallback = noop; |
450 | }); | 491 | }); |
451 | 492 | ||
493 | // Handle synchronous messages from service webviews. | ||
494 | |||
495 | ipcMain.on('find-in-page', (e, text, options) => { | ||
496 | const { sender: webContents } = e; | ||
497 | if (webContents !== mainWindow.webContents && typeof text === 'string') { | ||
498 | const sanitizedOptions = {}; | ||
499 | for (const option of ['forward', 'findNext', 'matchCase']) { | ||
500 | if (option in options) { | ||
501 | sanitizedOptions[option] = !!options[option]; | ||
502 | } | ||
503 | } | ||
504 | const requestId = webContents.findInPage(text, sanitizedOptions); | ||
505 | debug('Find in page', text, options, requestId); | ||
506 | e.returnValue = requestId; | ||
507 | } else { | ||
508 | e.returnValue = null; | ||
509 | } | ||
510 | }); | ||
511 | |||
512 | ipcMain.on('stop-find-in-page', (e, action) => { | ||
513 | const { sender: webContents } = e; | ||
514 | if (webContents !== mainWindow.webContents) { | ||
515 | const validActions = [ | ||
516 | 'clearSelection', | ||
517 | 'keepSelection', | ||
518 | 'activateSelection', | ||
519 | ]; | ||
520 | if (validActions.includes(action)) { | ||
521 | webContents.stopFindInPage(action); | ||
522 | } | ||
523 | } | ||
524 | e.returnValue = null; | ||
525 | }); | ||
526 | |||
452 | // Quit when all windows are closed. | 527 | // Quit when all windows are closed. |
453 | app.on('window-all-closed', () => { | 528 | app.on('window-all-closed', () => { |
454 | // On OS X it is common for applications and their menu bar | 529 | // On OS X it is common for applications and their menu bar |
455 | // to stay active until the user quits explicitly with Cmd + Q | 530 | // to stay active until the user quits explicitly with Cmd + Q |
456 | if (settings.get('runInBackground') === undefined | 531 | if ( |
457 | || settings.get('runInBackground')) { | 532 | settings.get('runInBackground') === undefined || |
533 | settings.get('runInBackground') | ||
534 | ) { | ||
458 | debug('Window: all windows closed, quit app'); | 535 | debug('Window: all windows closed, quit app'); |
459 | app.quit(); | 536 | app.quit(); |
460 | } else { | 537 | } else { |
461 | debug('Window: don\'t quit app'); | 538 | debug("Window: don't quit app"); |
462 | } | 539 | } |
463 | }); | 540 | }); |
464 | 541 | ||
@@ -487,7 +564,7 @@ app.on('will-finish-launching', () => { | |||
487 | app.on('open-url', (event, url) => { | 564 | app.on('open-url', (event, url) => { |
488 | event.preventDefault(); | 565 | event.preventDefault(); |
489 | 566 | ||
490 | onDidLoad((window) => { | 567 | onDidLoad(window => { |
491 | debug('open-url event', url); | 568 | debug('open-url event', url); |
492 | handleDeepLink(window, url); | 569 | handleDeepLink(window, url); |
493 | }); | 570 | }); |