aboutsummaryrefslogtreecommitdiffstats
path: root/src/index.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/index.js')
-rw-r--r--src/index.js199
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
3import { 3import { app, BrowserWindow, shell, ipcMain, session } from 'electron';
4 app, 4
5 BrowserWindow, 5import { emptyDirSync, ensureFileSync } from 'fs-extra';
6 shell, 6import { join } from 'path';
7 ipcMain,
8 session,
9} from 'electron';
10
11import fs from 'fs-extra';
12import path from 'path';
13import windowStateKeeper from 'electron-window-state'; 7import windowStateKeeper from 'electron-window-state';
14import { enforceMacOSAppLocation } from 'electron-util'; 8import { enforceMacOSAppLocation } from 'electron-util';
15import ms from 'ms'; 9import 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
30import { mainIpcHandler as basicAuthHandler } from './features/basicAuth'; 26import { 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
72fs.emptyDirSync(path.join(app.getPath('userData'), 'recipes', 'temp')); 68emptyDirSync(userDataRecipesPath('temp'));
73fs.ensureFileSync(path.join(app.getPath('userData'), 'window-state.json')); 69ensureFileSync(userDataPath('window-state.json'));
74 70
75// Set App ID for Windows 71// Set App ID for Windows
76if (isWindows) { 72if (isWindows) {
@@ -90,7 +86,9 @@ if (settings.get('sentry')) {
90const liftSingleInstanceLock = settings.get('liftSingleInstanceLock') || false; 86const liftSingleInstanceLock = settings.get('liftSingleInstanceLock') || false;
91 87
92// Force single window 88// Force single window
93const gotTheLock = liftSingleInstanceLock ? true : app.requestSingleInstanceLock(); 89const gotTheLock = liftSingleInstanceLock
90 ? true
91 : app.requestSingleInstanceLock();
94if (!gotTheLock) { 92if (!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
138if (isLinux && ['Pantheon', 'Unity:Unity7'].indexOf(process.env.XDG_CURRENT_DESKTOP) !== -1) { 142if (
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 = () => {
351const argv = require('minimist')(process.argv.slice(1)); 365const argv = require('minimist')(process.argv.slice(1));
352 366
353if (argv['auth-server-whitelist']) { 367if (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}
356if (argv['auth-negotiate-delegate-whitelist']) { 373if (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
362app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling,MediaSessionService,CrossOriginOpenerPolicy'); 382app.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
421ipcMain.on('open-browser-window', (e, { url, serviceId }) => { 454ipcMain.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
429ipcMain.on('modifyRequestHeaders', (e, { modifiedRequestHeaders, serviceId }) => { 465ipcMain.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
445ipcMain.on('feature-basic-auth-cancel', () => { 486ipcMain.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
495ipcMain.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
512ipcMain.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.
453app.on('window-all-closed', () => { 528app.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 });