text: let perfTimestamps = [{ name: 'pre-init', ts: process.hrtime() }];
  
  if (process.send && process.argv.includes('--native-module-host')) {
      require('./native-module-host').startInOwnProcess();
      return;
  }
  
  const electron = require('electron');
  const path = require('path');
  const fs = require('fs');
  const url = require('url');
  
  const { locale, setLocale, getLocaleValues } = require('./scripts/locale');
  const { Logger } = require('./scripts/logger');
  const { isDev } = require('./scripts/util/app-info');
  
  perfTimestamps?.push({ name: 'loading app requires', ts: process.hrtime() });
  
  const main = electron.app;
  const logger = new Logger('remote-app');
  
  let mainWindow = null;
  let appIcon = null;
  let ready = false;
  let appReady = false;
  let pendingUpdateFilePath;
  let mainWindowPosition = {};
  let updateMainWindowPositionTimeout = null;
  let mainWindowMaximized = false;
  
  const windowPositionFileName = 'window-position.json';
  const portableConfigFileName = 'keeweb-portable.json';
  
  const startupLogging =
      process.argv.some((arg) => arg.startsWith('--startup-logging')) ||
      process.env.KEEWEB_STARTUP_LOGGING === '1';
  
  const gotTheLock = main.requestSingleInstanceLock();
  if (!gotTheLock) {
      main.quit();
  }
  
  logProgress('single instance lock');
  
  let usingPortableUserDataDir = false;
  let execPath;
  
  setUserDataPaths();
  
  let openFile = process.argv.filter((arg) => /\.kdbx$/i.test(arg))[0];
  
  const htmlPath =
      (isDev && process.env.KEEWEB_HTML_PATH) ||
      url.format({ protocol: 'file', slashes: true, pathname: path.join(__dirname, 'index.html') });
  
  const showDevToolsOnStart =
      process.argv.some((arg) => arg.startsWith('--devtools')) ||
      process.env.KEEWEB_OPEN_DEVTOOLS === '1';
  
  const loginItemSettings = process.platform === 'darwin' ? main.getLoginItemSettings() : {};
  
  const startMinimized =
      loginItemSettings.wasOpenedAsHidden ||
      process.argv.some((arg) => arg.startsWith('--minimized'));
  
  const themeBgColors = {
      dark: '#1e1e1e',
      light: '#f6f6f6',
      db: '#342f2e',
      fb: '#282c34',
      wh: '#fafafa',
      te: '#222',
      hc: '#fafafa',
      sd: '#002b36',
      sl: '#fdf6e3'
  };
  const darkLightThemes = {
      dark: 'light',
      sd: 'sl',
      fb: 'bl',
      db: 'lb',
      te: 'lt',
      dc: 'hc'
  };
  const defaultBgColor = '#282C34';
  
  logProgress('defining args');
  
  setEnv();
  setDevAppIcon();
  
  let configEncryptionKey;
  let appSettings;
  
  const settingsPromise = loadSettingsEncryptionKey().then((key) => {
      configEncryptionKey = key;
      logProgress('loading settings key');
  
      return loadConfig('app-settings').then((settings) => {
          try {
              appSettings = settings ? JSON.parse(settings) : {};
          } catch (e) {
              logStartupMessage(`Error loading app settings: ${e}`);
          }
          logProgress('reading app settings');
      });
  });
  
  main.on('window-all-closed', () => {
      if (pendingUpdateFilePath) {
          exitAndStartUpdate();
      } else {
          if (process.platform !== 'darwin') {
              main.quit();
          }
      }
  });
  main.on('ready', () => {
      logProgress('app on ready');
      appReady = true;
  
      settingsPromise
          .then(() => {
              createMainWindow();
              setupIpcHandlers();
              setGlobalShortcuts(appSettings);
              subscribePowerEvents();
              hookRequestHeaders();
  
              loadLocale().then(() => {
                  setMenu();
              });
          })
          .catch((e) => {
              electron.dialog.showErrorBox('KeeWeb', 'Error loading app: ' + e);
              main.exit(2);
          });
  });
  main.on('open-file', (e, path) => {
      e.preventDefault();
      openFile = path;
      notifyOpenFile();
  });
  main.on('activate', () => {
      if (process.platform === 'darwin') {
          if (appReady && !mainWindow && appSettings) {
              createMainWindow();
          } else if (appIcon) {
              restoreMainWindow();
          }
      }
  });
  main.on('before-quit', (e) => {
      if (main.hookBeforeQuitEvent && mainWindow) {
          e.preventDefault();
          emitRemoteEvent('launcher-before-quit');
      }
  });
  main.on('will-quit', () => {
      electron.globalShortcut.unregisterAll();
  });
  main.on('second-instance', () => {
      if (mainWindow) {
          restoreMainWindow();
      }
  });
  main.on('web-contents-created', (event, contents) => {
      contents.setWindowOpenHandler((e) => {
          logger.warn(`Prevented new window: ${e.url}`);
          emitRemoteEvent('log', `Prevented new window: ${e.url}`);
          return { action: 'deny' };
      });
      contents.on('will-navigate', (e, url) => {
          if (!url.startsWith('https://beta.keeweb.info/') && !url.startsWith(htmlPath)) {
              e.preventDefault();
              logger.warn(`Prevented navigation: ${url}`);
          }
      });
  });
  main.restartAndUpdate = function (updateFilePath) {
      pendingUpdateFilePath = updateFilePath;
      mainWindow.close();
      setTimeout(() => {
          pendingUpdateFilePath = undefined;
      }, 1000);
  };
  main.minimizeApp = function (menuItemLabels) {
      let imagePath;
      // a workaround to correctly restore focus on windows platform
      // without this workaround, focus is not restored to the previously focused field
      if (process.platform === 'win32') {
          mainWindow.minimize();
      }
      mainWindow.hide();
      if (process.platform === 'darwin') {
          main.dock.hide();
          imagePath = 'macOS-MenubarTemplate.png';
      } else {
          imagePath = 'icon.png';
      }
      mainWindow.setSkipTaskbar(true);
      if (!appIcon) {
          const image = electron.nativeImage.createFromPath(path.join(__dirname, 'img', imagePath));
          appIcon = new electron.Tray(image);
          if (process.platform !== 'darwin') {
              appIcon.on('click', restoreMainWindow);
          }
          const contextMenu = electron.Menu.buildFromTemplate([
              { label: menuItemLabels.restore, click: restoreMainWindow },
              { label: menuItemLabels.quit, click: closeMainWindow }
          ]);
          appIcon.setContextMenu(contextMenu);
          appIcon.setToolTip('KeeWeb');
      }
  };
  main.minimizeThenHideIfInTray = function () {
      // This function is called when auto-type has displayed a selection list and a selection was made.
      // To ensure focus returns to the previous window we must minimize first even if we're going to hide.
      mainWindow.minimize();
      if (appIcon) mainWindow.hide();
  };
  main.getMainWindow = function () {
      return mainWindow;
  };
  main.setHookBeforeQuitEvent = (hooked) => {
      main.hookBeforeQuitEvent = !!hooked;
  };
  main.setGlobalShortcuts = setGlobalShortcuts;
  main.showAndFocusMainWindow = showAndFocusMainWindow;
  main.loadConfig = loadConfig;
  main.saveConfig = saveConfig;
  main.getAppMainRoot = getAppMainRoot;
  main.getAppContentRoot = getAppContentRoot;
  main.httpRequest = httpRequest;
  
  function logProgress(name) {
      perfTimestamps?.push({ name, ts: process.hrtime() });
      logStartupMessage(name);
  }
  
  function logStartupMessage(msg) {
      if (startupLogging) {
          // eslint-disable-next-line no-console
          console.log('[startup]', msg);
      }
  }
  
  function checkSettingsTheme(theme) {
      // old settings migration
      if (theme === 'macdark') {
          return 'dark';
      }
      if (theme === 'wh') {
          return 'light';
      }
      return theme;
  }
  
  function getDefaultTheme() {
      return 'dark';
  }
  
  function selectDarkOrLightTheme(theme) {
      const dark = electron.nativeTheme.shouldUseDarkColors;
      for (const [darkTheme, lightTheme] of Object.entries(darkLightThemes)) {
          if (darkTheme === theme || lightTheme === theme) {
              return dark ? darkTheme : lightTheme;
          }
      }
      return theme;
  }
  
  function createMainWindow() {
      let theme = checkSettingsTheme(appSettings.theme) || getDefaultTheme();
      if (appSettings.autoSwitchTheme) {
          theme = selectDarkOrLightTheme(theme);
      }
      const bgColor = themeBgColors[theme] || defaultBgColor;
  
      const isWindows = process.platform === 'win32';
      let titlebarStyle = appSettings.titlebarStyle;
      if (titlebarStyle === 'hidden-inset') {
          titlebarStyle = 'hiddenInset';
      }
      const frameless = isWindows && ['hidden', 'hiddenInset'].includes(titlebarStyle);
  
      const windowOptions = {
          show: false,
          width: 1000,
          height: 700,
          minWidth: 700,
          minHeight: 400,
          titleBarStyle: titlebarStyle,
          frame: !frameless,
          backgroundColor: bgColor,
          webPreferences: {
              contextIsolation: false,
              backgroundThrottling: false,
              nodeIntegration: true,
              nodeIntegrationInWorker: true,
              enableRemoteModule: true,
              spellcheck: false,
              v8CacheOptions: 'none'
          }
      };
      if (process.platform !== 'win32') {
          windowOptions.icon = path.join(__dirname, 'img', 'icon.png');
      }
      mainWindow = new electron.BrowserWindow(windowOptions);
      logProgress('creating main window');
  
      mainWindow.loadURL(htmlPath);
      mainWindow.once('ready-to-show', () => {
          logProgress('main window ready');
          if (startMinimized) {
              emitRemoteEvent('launcher-started-minimized');
          } else {
              mainWindow.show();
          }
          ready = true;
          notifyOpenFile();
          logProgress('main window shown');
          reportStartProfile();
  
          if (showDevToolsOnStart) {
              mainWindow.webContents.openDevTools({ mode: 'bottom' });
          }
      });
      mainWindow.webContents.on('context-menu', onContextMenu);
      mainWindow.on('resize', delaySaveMainWindowPosition);
      mainWindow.on('move', delaySaveMainWindowPosition);
      mainWindow.on('restore', coerceMainWindowPositionToConnectedDisplay);
      mainWindow.on('close', mainWindowClosing);
      mainWindow.on('closed', mainWindowClosed);
      mainWindow.on('focus', mainWindowFocus);
      mainWindow.on('blur', mainWindowBlur);
      mainWindow.on('closed', () => {
          mainWindow = null;
          saveMainWindowPosition();
      });
      mainWindow.on('minimize', () => {
          emitRemoteEvent('launcher-minimize');
      });
      mainWindow.on('maximize', () => {
          mainWindowMaximized = true;
          emitRemoteEvent('launcher-maximize');
      });
      mainWindow.on('unmaximize', () => {
          mainWindowMaximized = false;
          emitRemoteEvent('launcher-unmaximize');
      });
      mainWindow.on('leave-full-screen', () => {
          emitRemoteEvent('leave-full-screen');
      });
      mainWindow.on('enter-full-screen', () => {
          emitRemoteEvent('enter-full-screen');
      });
      mainWindow.on('session-end', () => {
          emitRemoteEvent('os-lock');
      });
      logProgress('configuring main window');
  
      restoreMainWindowPosition();
      logProgress('restoring main window position');
  }
  
  function restoreMainWindow() {
      if (process.platform === 'darwin' && !main.dock.isVisible()) {
          main.dock.show();
      }
      if (mainWindow.isMinimized()) {
          mainWindow.restore();
      }
      mainWindow.setSkipTaskbar(false);
      mainWindow.show();
      coerceMainWindowPositionToConnectedDisplay();
      setTimeout(destroyAppIcon, 0);
  }
  
  function showAndFocusMainWindow() {
      if (appIcon) {
          restoreMainWindow();
      }
      if (mainWindowMaximized) {
          mainWindow.maximize();
      } else {
          mainWindow.show();
      }
      mainWindow.focus();
      if (process.platform === 'darwin' && !main.dock.isVisible()) {
          main.dock.show();
      }
  }
  
  function closeMainWindow() {
      emitRemoteEvent('launcher-exit-request');
      setTimeout(destroyAppIcon, 0);
  }
  
  function destroyAppIcon() {
      if (appIcon) {
          appIcon.destroy();
          appIcon = null;
      }
  }
  
  function delaySaveMainWindowPosition() {
      if (updateMainWindowPositionTimeout) {
          clearTimeout(updateMainWindowPositionTimeout);
      }
      updateMainWindowPositionTimeout = setTimeout(updateMainWindowPosition, 500);
  }
  
  function updateMainWindowPositionIfPending() {
      if (updateMainWindowPositionTimeout) {
          clearTimeout(updateMainWindowPositionTimeout);
          updateMainWindowPosition();
      }
  }
  
  function updateMainWindowPosition() {
      if (!mainWindow) {
          return;
      }
      updateMainWindowPositionTimeout = null;
      const bounds = mainWindow.getBounds();
      if (!mainWindow.isMaximized() && !mainWindow.isMinimized() && !mainWindow.isFullScreen()) {
          mainWindowPosition.x = bounds.x;
          mainWindowPosition.y = bounds.y;
          mainWindowPosition.width = bounds.width;
          mainWindowPosition.height = bounds.height;
      }
      mainWindowPosition.maximized = mainWindow.isMaximized();
      mainWindowPosition.fullScreen = mainWindow.isFullScreen();
      mainWindowPosition.changed = true;
  }
  
  function saveMainWindowPosition() {
      if (!mainWindowPosition.changed) {
          return;
      }
      delete mainWindowPosition.changed;
      try {
          fs.writeFileSync(
              path.join(main.getPath('userData'), windowPositionFileName),
              JSON.stringify(mainWindowPosition),
              'utf8'
          );
      } catch (e) {}
  }
  
  function restoreMainWindowPosition() {
      const fileName = path.join(main.getPath('userData'), windowPositionFileName);
      fs.readFile(fileName, 'utf8', (e, data) => {
          if (data) {
              try {
                  mainWindowPosition = JSON.parse(data);
              } catch (e) {
                  logStartupMessage(`Error loading main window position: ${e}`);
              }
              if (mainWindow && mainWindowPosition) {
                  if (mainWindowPosition.width && mainWindowPosition.height) {
                      mainWindow.setBounds(mainWindowPosition);
                      coerceMainWindowPositionToConnectedDisplay();
                  }
                  if (mainWindowPosition.maximized) {
                      mainWindow.maximize();
                      mainWindowMaximized = true;
                  }
                  if (mainWindowPosition.fullScreen) {
                      mainWindow.setFullScreen(true);
                  }
              }
          }
      });
  }
  
  function mainWindowBlur() {
      emitRemoteEvent('main-window-blur');
  }
  
  function mainWindowFocus() {
      emitRemoteEvent('main-window-focus');
  }
  
  function mainWindowClosing() {
      updateMainWindowPositionIfPending();
  }
  
  function mainWindowClosed() {
      main.removeAllListeners('remote-app-event');
  }
  
  function emitRemoteEvent(e, arg) {
      if (mainWindow && mainWindow.webContents) {
          main.emit('remote-app-event', {
              name: e,
              data: arg
          });
      }
  }
  
  function setMenu() {
      if (process.platform === 'darwin') {
          const name = require('electron').app.name;
          const template = [
              {
                  label: name,
                  submenu: [
                      { role: 'about', label: locale.sysMenuAboutKeeWeb?.replace('{}', 'KeeWeb') },
                      { type: 'separator' },
                      { role: 'services', submenu: [], label: locale.sysMenuServices },
                      { type: 'separator' },
                      {
                          accelerator: 'Command+H',
                          role: 'hide',
                          label: locale.sysMenuHide?.replace('{}', 'KeeWeb')
                      },
                      {
                          accelerator: 'Command+Shift+H',
                          role: 'hideothers',
                          label: locale.sysMenuHideOthers
                      },
                      { role: 'unhide', label: locale.sysMenuUnhide },
                      { type: 'separator' },
                      {
                          role: 'quit',
                          accelerator: 'Command+Q',
                          label: locale.sysMenuQuit?.replace('{}', 'KeeWeb')
                      }
                  ]
              },
              {
                  label: locale.sysMenuEdit || 'Edit',
                  submenu: [
                      { accelerator: 'CmdOrCtrl+Z', role: 'undo', label: locale.sysMenuUndo },
                      { accelerator: 'Shift+CmdOrCtrl+Z', role: 'redo', label: locale.sysMenuRedo },
                      { type: 'separator' },
                      { accelerator: 'CmdOrCtrl+X', role: 'cut', label: locale.sysMenuCut },
                      { accelerator: 'CmdOrCtrl+C', role: 'copy', label: locale.sysMenuCopy },
                      { accelerator: 'CmdOrCtrl+V', role: 'paste', label: locale.sysMenuPaste },
                      {
                          accelerator: 'CmdOrCtrl+A',
                          role: 'selectall',
                          label: locale.sysMenuSelectAll
                      }
                  ]
              },
              {
                  label: locale.sysMenuWindow || 'Window',
                  submenu: [
                      { accelerator: 'CmdOrCtrl+M', role: 'minimize', label: locale.sysMenuMinimize },
                      { accelerator: 'Command+W', role: 'close', label: locale.sysMenuClose }
                  ]
              }
          ];
          const menu = electron.Menu.buildFromTemplate(template);
          electron.Menu.setApplicationMenu(menu);
      } else {
          mainWindow.setMenuBarVisibility(false);
          mainWindow.setMenu(null);
          electron.Menu.setApplicationMenu(null);
      }
  }
  
  function onContextMenu(e, props) {
      if (props.inputFieldType !== 'plainText' || !props.isEditable) {
          return;
      }
      const Menu = electron.Menu;
      const inputMenu = Menu.buildFromTemplate([
          { role: 'undo' },
          { role: 'redo' },
          { type: 'separator' },
          { role: 'cut' },
          { role: 'copy' },
          { role: 'paste' },
          { type: 'separator' },
          { role: 'selectall' }
      ]);
      inputMenu.popup(mainWindow);
  }
  
  function notifyOpenFile() {
      if (ready && openFile && mainWindow) {
          const openKeyfile = process.argv
              .filter((arg) => arg.startsWith('--keyfile='))
              .map((arg) => arg.replace('--keyfile=', ''))[0];
          const fileInfo = JSON.stringify({ data: openFile, key: openKeyfile });
          mainWindow.webContents.executeJavaScript(
              'if (window.launcherOpen) { window.launcherOpen(' +
                  fileInfo +
                  '); } ' +
                  ' else { window.launcherOpenedFile=' +
                  fileInfo +
                  '; }'
          );
          openFile = null;
      }
  }
  
  function setGlobalShortcuts(appSettings) {
      const defaultShortcutModifiers = process.platform === 'darwin' ? 'Ctrl+Alt+' : 'Shift+Alt+';
      const defaultShortcuts = {
          AutoType: { shortcut: defaultShortcutModifiers + 'T', event: 'auto-type' },
          CopyPassword: { shortcut: defaultShortcutModifiers + 'C', event: 'copy-password' },
          CopyUser: { shortcut: defaultShortcutModifiers + 'B', event: 'copy-user' },
          CopyUrl: { shortcut: defaultShortcutModifiers + 'U', event: 'copy-url' },
          CopyOtp: { event: 'copy-otp' },
          RestoreApp: { action: restoreMainWindow }
      };
      electron.globalShortcut.unregisterAll();
      for (const [key, shortcutDef] of Object.entries(defaultShortcuts)) {
          const fromSettings = appSettings[`globalShortcut${key}`];
          const shortcut = fromSettings || shortcutDef.shortcut;
          if (shortcut) {
              try {
                  electron.globalShortcut.register(shortcut, () => {
                      if (shortcutDef.event) {
                          emitRemoteEvent(shortcutDef.event);
                      }
                      if (shortcutDef.action) {
                          shortcutDef.action();
                      }
                  });
              } catch (e) {}
          }
      }
      logProgress('setting global shortcuts');
  }
  
  function subscribePowerEvents() {
      electron.powerMonitor.on('suspend', () => {
          emitRemoteEvent('power-monitor-suspend');
      });
      electron.powerMonitor.on('resume', () => {
          emitRemoteEvent('power-monitor-resume');
      });
      electron.powerMonitor.on('lock-screen', () => {
          emitRemoteEvent('os-lock');
      });
      logProgress('subscribing to power events');
  }
  
  function setUserDataPaths() {
      execPath = process.execPath;
  
      let isPortable = false;
  
      switch (process.platform) {
          case 'darwin':
              isPortable = !execPath.includes('/Applications/');
              if (isPortable) {
                  execPath = execPath.substring(0, execPath.indexOf('.app'));
              }
              break;
          case 'win32':
              isPortable = !execPath.includes('Program Files');
              break;
          case 'linux':
              isPortable = !execPath.startsWith('/usr/') && !execPath.startsWith('/opt/');
              break;
      }
  
      if (isDev && process.env.KEEWEB_IS_PORTABLE) {
          try {
              isPortable = !!JSON.parse(process.env.KEEWEB_IS_PORTABLE);
          } catch {}
      }
  
      logProgress('portable check');
  
      if (isPortable) {
          const portableConfigDir = path.dirname(execPath);
          const portableConfigPath = path.join(portableConfigDir, portableConfigFileName);
  
          if (fs.existsSync(portableConfigPath)) {
              try {
                  const portableConfig = JSON.parse(fs.readFileSync(portableConfigPath, 'utf8'));
                  const portableUserDataDir = path.resolve(
                      portableConfigDir,
                      portableConfig.userDataDir
                  );
  
                  if (!fs.existsSync(portableUserDataDir)) {
                      fs.mkdirSync(portableUserDataDir, { recursive: true });
                  }
  
                  main.setPath('userData', portableUserDataDir);
                  usingPortableUserDataDir = true;
              } catch (e) {
                  logStartupMessage(`Error loading portable config: ${e}`);
              }
          }
      }
  
      logProgress('userdata dir');
  }
  
  function setEnv() {
      if (
          process.platform === 'linux' &&
          ['Pantheon', 'Unity:Unity7'].indexOf(process.env.XDG_CURRENT_DESKTOP) !== -1
      ) {
          // https://github.com/electron/electron/issues/9046
          process.env.XDG_CURRENT_DESKTOP = 'Unity';
      }
  
      main.commandLine.appendSwitch('disable-background-timer-throttling');
  
      // disable all caching, since we're not using old profile data anyway
      main.commandLine.appendSwitch('disable-http-cache');
      main.commandLine.appendSwitch('disable-gpu-shader-disk-cache');
  
      if (process.platform === 'linux') {
          // fixes colors on Linux, see #1621
          main.commandLine.appendSwitch('force-color-profile', 'srgb');
      }
  
      main.allowRendererProcessReuse = true;
  
      logProgress('setting env');
  }
  
  function setDevAppIcon() {
      if (isDev && htmlPath && process.platform === 'darwin') {
          const icon = electron.nativeImage.createFromPath(
              path.join(__dirname, '../graphics/512x512.png')
          );
          main.dock.setIcon(icon);
      }
  }
  
  // When sending a PUT XMLHttpRequest Chromium includes the header "Origin: file://".
  // This confuses some WebDAV clients, notably OwnCloud.
  // The header is invalid, so removing it everywhere it occurs should do no harm.
  
  function hookRequestHeaders() {
      electron.session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
          if (
              !details.url.startsWith('ws:') &&
              !details.url.startsWith('https://plugins.keeweb.info/')
          ) {
              delete details.requestHeaders.Origin;
          }
          callback({ requestHeaders: details.requestHeaders });
      });
      logProgress('setting request handlers');
  }
  
  // If a display is disconnected while KeeWeb is minimized, Electron does not
  // ensure that the restored window appears on a display that is still connected.
  // This checks to be sure the title bar is somewhere the user can grab it,
  // without making it impossible to minimize and restore a window keeping it
  // partially off-screen or straddling two displays if the user desires that.
  
  function coerceMainWindowPositionToConnectedDisplay() {
      const eScreen = electron.screen;
      const displays = eScreen.getAllDisplays();
      if (!displays || !displays.length) return;
      const windowBounds = mainWindow.getBounds();
      const contentBounds = mainWindow.getContentBounds();
      const tbLeft = windowBounds.x;
      const tbRight = windowBounds.x + windowBounds.width;
      const tbTop = windowBounds.y;
      const tbBottom = contentBounds.y;
      // 160px width and 2/3s the title bar height should be enough that the user can grab it
      for (let i = 0; i < displays.length; ++i) {
          const workArea = displays[i].workArea;
          const overlapWidth =
              Math.min(tbRight, workArea.x + workArea.width) - Math.max(tbLeft, workArea.x);
          const overlapHeight =
              Math.min(tbBottom, workArea.y + workArea.height) - Math.max(tbTop, workArea.y);
          if (overlapWidth >= 160 && 3 * overlapHeight >= 2 * (tbBottom - tbTop)) return;
      }
      // If we get here, no display contains a big enough strip of the title bar
      // that we can be confident the user can drag it into visibility.  Rather than
      // attempt to guess what the user wants, just center it on the primary display.
      // Try to keep the previous height and width, but clamp each to 90% of the workarea.
      const workArea = eScreen.getPrimaryDisplay().workArea;
      const newWidth = Math.min(windowBounds.width, Math.floor(0.9 * workArea.width));
      const newHeight = Math.min(windowBounds.height, Math.floor(0.9 * workArea.height));
      mainWindow.setBounds({
          'x': workArea.x + Math.floor((workArea.width - newWidth) / 2),
          'y': workArea.y + Math.floor((workArea.height - newHeight) / 2),
          'width': newWidth,
          'height': newHeight
      });
      updateMainWindowPosition();
  }
  
  function reportStartProfile() {
      if (!perfTimestamps) {
          return;
      }
  
      const processCreationTime = process.getCreationTime();
      const totalTime = Math.round(Date.now() - processCreationTime);
      let lastTs = 0;
      const timings = perfTimestamps
          .map((milestone) => {
              const ts = milestone.ts;
              const elapsed = lastTs
                  ? Math.round((ts[0] - lastTs[0]) * 1e3 + (ts[1] - lastTs[1]) / 1e6)
                  : 0;
              lastTs = ts;
              return {
                  name: milestone.name,
                  elapsed
              };
          })
          .slice(1);
  
      perfTimestamps = undefined;
  
      const startProfile = { totalTime, timings };
      emitRemoteEvent('start-profile', startProfile);
  }
  
  function getAppMainRoot() {
      if (isDev) {
          return __dirname;
      } else {
          return process.mainModule.path;
      }
  }
  
  function getAppContentRoot() {
      return __dirname;
  }
  
  function reqNative(mod) {
      const fileName = `${mod}-${process.platform}-${process.arch}.node`;
  
      const modulePath = `../node_modules/@keeweb/keeweb-native-modules/${fileName}`;
      const fullPath = path.join(getAppMainRoot(), modulePath);
  
      return require(fullPath);
  }
  
  function loadSettingsEncryptionKey() {
      return Promise.resolve().then(() => {
          if (usingPortableUserDataDir) {
              return null;
          }
  
          const keytar = reqNative('keytar');
  
          return keytar.getPassword('KeeWeb', 'settings-key').then((key) => {
              if (key) {
                  return Buffer.from(key, 'hex');
              }
              key = require('crypto').randomBytes(48);
              return keytar.setPassword('KeeWeb', 'settings-key', key.toString('hex')).then(() => {
                  return migrateOldConfigs(key).then(() => key);
              });
          });
      });
  }
  
  function loadConfig(name) {
      const ext = configEncryptionKey ? 'dat' : 'json';
      const configFilePath = path.join(main.getPath('userData'), `${name}.${ext}`);
  
      return new Promise((resolve, reject) => {
          fs.readFile(configFilePath, (err, data) => {
              if (err) {
                  if (err.code === 'ENOENT') {
                      resolve(null);
                  } else {
                      reject(`Error reading config ${name}: ${err}`);
                  }
                  return;
              }
  
              try {
                  if (configEncryptionKey) {
                      const key = configEncryptionKey.slice(0, 32);
                      const iv = configEncryptionKey.slice(32, 48);
  
                      const crypto = require('crypto');
                      const cipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
  
                      data = Buffer.concat([cipher.update(data), cipher.final()]);
                  }
  
                  resolve(data.toString('utf8'));
              } catch (err) {
                  logStartupMessage(`Error reading config data (config ignored) ${name}: ${err}`);
                  resolve(null);
              }
          });
      });
  }
  
  function saveConfig(name, data, key) {
      if (!key) {
          key = configEncryptionKey;
      }
  
      return new Promise((resolve, reject) => {
          try {
              data = Buffer.from(data);
  
              if (key) {
                  const crypto = require('crypto');
                  const cipher = crypto.createCipheriv(
                      'aes-256-cbc',
                      key.slice(0, 32),
                      key.slice(32, 48)
                  );
  
                  data = Buffer.concat([cipher.update(data), cipher.final()]);
              }
          } catch (err) {
              return reject(`Error writing config data ${name}: ${err}`);
          }
  
          const ext = key ? 'dat' : 'json';
          const configFilePath = path.join(main.getPath('userData'), `${name}.${ext}`);
          fs.writeFile(configFilePath, data, (err) => {
              if (err) {
                  reject(`Error writing config ${name}: ${err}`);
              } else {
                  resolve();
              }
          });
      });
  }
  
  function loadLocale() {
      return loadConfig('locale').then((localeValues) => {
          if (localeValues) {
              try {
                  localeValues = JSON.parse(localeValues);
                  if (appSettings?.locale === localeValues?.locale) {
                      setLocale(localeValues);
                  }
              } catch (e) {
                  logStartupMessage(`Error loading locale: ${e}`);
              }
          }
          locale.on('changed', () => {
              setMenu();
              saveConfig('locale', JSON.stringify(getLocaleValues()));
          });
      });
  }
  
  // TODO: delete in 2021
  function migrateOldConfigs(key) {
      const knownConfigs = [
          'file-info',
          'app-settings',
          'runtime-data',
          'update-info',
          'plugin-gallery',
          'plugins'
      ];
  
      const promises = [];
  
      for (const configName of knownConfigs) {
          promises.push(
              loadConfig(configName).then((data) => {
                  if (data) {
                      return saveConfig(configName, data, key).then(() => {
                          fs.unlinkSync(path.join(main.getPath('userData'), `${configName}.json`));
                      });
                  }
              })
          );
      }
  
      return Promise.all(promises);
  }
  
  function httpRequest(config, log, onLoad) {
      // eslint-disable-next-line node/no-deprecated-api
      const opts = url.parse(config.url);
  
      opts.method = config.method || 'GET';
      opts.headers = {
          'User-Agent': mainWindow.webContents.userAgent,
          ...config.headers
      };
      opts.timeout = 60000;
  
      let data;
      if (config.data) {
          if (config.dataIsMultipart) {
              data = Buffer.concat(config.data.map((chunk) => Buffer.from(chunk)));
          } else {
              data = Buffer.from(config.data);
          }
          // Electron's API doesn't like that, while node.js needs it
          // opts.headers['Content-Length'] = data.byteLength;
      }
  
      const req = electron.net.request(opts);
  
      req.on('response', (res) => {
          const chunks = [];
          const onClose = () => {
              log('info', 'HTTP response', opts.method, config.url, res.statusCode, res.headers);
              onLoad({
                  status: res.statusCode,
                  response: Buffer.concat(chunks).toString('hex'),
                  headers: res.headers
              });
          };
          res.on('data', (chunk) => {
              chunks.push(chunk);
          });
          res.on('end', () => {
              onClose();
          });
      });
      req.on('error', (e) => {
          log('error', 'HTTP error', opts.method, config.url, e);
          return config.error && config.error('network error', {});
      });
      req.on('timeout', () => {
          req.abort();
          return config.error && config.error('timeout', {});
      });
      if (data) {
          req.write(data);
      }
      req.end();
  }
  
  function setupIpcHandlers() {
      const { setupIpcHandlers } = require('./scripts/ipc');
      setupIpcHandlers();
      logProgress('setting ipc handlers');
  }
  
  function exitAndStartUpdate() {
      if (pendingUpdateFilePath) {
          const { installUpdate } = require('./scripts/update-installer');
          installUpdate(pendingUpdateFilePath);
      }
  }
  