import fs from 'fs'; import path from 'path'; import { BrowserWindow, shell, ipcMain, dialog } from 'electron'; import windowStateKeeper from 'electron-window-state'; import helpers from './../../helpers/helpers'; import createMenu from './../menu/menu'; import initContextMenu from './../contextMenu/contextMenu'; const { isOSX, linkIsInternal, getCssToInject, shouldInjectCss, } = helpers; const ZOOM_INTERVAL = 0.1; function maybeHideWindow(window, event, fastQuit, tray) { if (isOSX() && !fastQuit) { // this is called when exiting from clicking the cross button on the window event.preventDefault(); window.hide(); } else if (!fastQuit && tray) { event.preventDefault(); window.hide(); } // will close the window on other platforms } function maybeInjectCss(browserWindow) { if (!shouldInjectCss()) { return; } const cssToInject = getCssToInject(); const injectCss = () => { browserWindow.webContents.insertCSS(cssToInject); }; browserWindow.webContents.on('did-finish-load', () => { // remove the injection of css the moment the page is loaded browserWindow.webContents.removeListener('did-get-response-details', injectCss); }); // on every page navigation inject the css browserWindow.webContents.on('did-navigate', () => { // we have to inject the css in did-get-response-details to prevent the fouc // will run multiple times browserWindow.webContents.on('did-get-response-details', injectCss); }); } /** * * @param {{}} inpOptions AppArgs from nativefier.json * @param {function} onAppQuit * @param {function} setDockBadge * @returns {electron.BrowserWindow} */ function createMainWindow(inpOptions, onAppQuit, setDockBadge) { const options = Object.assign({}, inpOptions); const mainWindowState = windowStateKeeper({ defaultWidth: options.width || 1280, defaultHeight: options.height || 800, }); const mainWindow = new BrowserWindow({ frame: !options.hideWindowFrame, width: mainWindowState.width, height: mainWindowState.height, minWidth: options.minWidth, minHeight: options.minHeight, maxWidth: options.maxWidth, maxHeight: options.maxHeight, x: mainWindowState.x, y: mainWindowState.y, autoHideMenuBar: !options.showMenuBar, // Convert dashes to spaces because on linux the app name is joined with dashes title: options.name, webPreferences: { javascript: true, plugins: true, // node globals causes problems with sites like messenger.com nodeIntegration: false, webSecurity: !options.insecure, preload: path.join(__dirname, 'static', 'preload.js'), zoomFactor: options.zoom, }, // after webpack path here should reference `resources/app/` icon: path.join(__dirname, '../', '/icon.png'), // set to undefined and not false because explicitly setting to false will disable full screen fullscreen: options.fullScreen || undefined, }); mainWindowState.manage(mainWindow); // after first run, no longer force maximize to be true if (options.maximize) { mainWindow.maximize(); options.maximize = undefined; fs.writeFileSync(path.join(__dirname, '..', 'nativefier.json'), JSON.stringify(options)); } let currentZoom = options.zoom; const onZoomIn = () => { currentZoom += ZOOM_INTERVAL; mainWindow.webContents.send('change-zoom', currentZoom); }; const onZoomOut = () => { currentZoom -= ZOOM_INTERVAL; mainWindow.webContents.send('change-zoom', currentZoom); }; const onZoomReset = () => { mainWindow.webContents.send('change-zoom', options.zoom); }; const clearAppData = () => { dialog.showMessageBox(mainWindow, { type: 'warning', buttons: ['Yes', 'Cancel'], defaultId: 1, title: 'Clear cache confirmation', message: 'This will clear all data (cookies, local storage etc) from this app. Are you sure you wish to proceed?', }, (response) => { if (response !== 0) { return; } const { session } = mainWindow.webContents; session.clearStorageData(() => { session.clearCache(() => { mainWindow.loadURL(options.targetUrl); }); }); }); }; const onGoBack = () => { mainWindow.webContents.goBack(); }; const onGoForward = () => { mainWindow.webContents.goForward(); }; const getCurrentUrl = () => mainWindow.webContents.getURL(); const menuOptions = { nativefierVersion: options.nativefierVersion, appQuit: onAppQuit, zoomIn: onZoomIn, zoomOut: onZoomOut, zoomReset: onZoomReset, zoomBuildTimeValue: options.zoom, goBack: onGoBack, goForward: onGoForward, getCurrentUrl, clearAppData, disableDevTools: options.disableDevTools, }; createMenu(menuOptions); if (!options.disableContextMenu) { initContextMenu(mainWindow); } if (options.userAgent) { mainWindow.webContents.setUserAgent(options.userAgent); } maybeInjectCss(mainWindow); mainWindow.webContents.on('did-finish-load', () => { mainWindow.webContents.send('params', JSON.stringify(options)); }); if (options.counter) { mainWindow.on('page-title-updated', (e, title) => { const itemCountRegex = /[([{](\d*?)\+?[}\])]/; const match = itemCountRegex.exec(title); if (match) { setDockBadge(match[1]); } else { setDockBadge(''); } }); } else { ipcMain.on('notification', () => { if (!isOSX() || mainWindow.isFocused()) { return; } setDockBadge('•'); }); mainWindow.on('focus', () => { setDockBadge(''); }); } mainWindow.webContents.on('new-window', (event, urlToGo) => { if (mainWindow.useDefaultWindowBehaviour) { mainWindow.useDefaultWindowBehaviour = false; return; } if (linkIsInternal(options.targetUrl, urlToGo, options.internalUrls)) { return; } event.preventDefault(); shell.openExternal(urlToGo); }); mainWindow.loadURL(options.targetUrl); mainWindow.on('close', (event) => { if (mainWindow.isFullScreen()) { mainWindow.setFullScreen(false); mainWindow.once('leave-full-screen', maybeHideWindow.bind(this, mainWindow, event, options.fastQuit)); } maybeHideWindow(mainWindow, event, options.fastQuit, options.tray); }); return mainWindow; } ipcMain.on('cancelNewWindowOverride', () => { const allWindows = BrowserWindow.getAllWindows(); allWindows.forEach((window) => { // eslint-disable-next-line no-param-reassign window.useDefaultWindowBehaviour = false; }); }); export default createMainWindow;