diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 015668b..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,49 +0,0 @@ -module.exports = { - globals: { - // mocha - describe: false, - it: false, - before: false, - beforeEach: false, - after: false, - afterEach: false - }, - rules: { - indent: [ - 2, - 4, - {SwitchCase: 1} - ], - quotes: [ - 2, - 'single' - ], - 'linebreak-style': [ - 2, - 'unix' - ], - semi: [ - 2, - 'always' - ], - 'max-len': 0, - 'require-jsdoc': 0, - 'padded-blocks': 0, - 'no-throw-literal': 0, - camelcase: 0, - 'valid-jsdoc': 0, - 'no-path-concat': 1, - 'quote-props': [2, 'as-needed'], - 'no-warning-comments': 1, - 'no-control-regex': 0 - }, - env: { - es6: true, - browser: true, - node: true - }, - ecmaFeatures: { - modules: true - }, - extends: 'google' -}; diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 0000000..eeafada --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,8 @@ +extends: airbnb-base +plugins: + - import +rules: + # TODO: Remove this when we have shifted away from the async package + no-shadow: 'warn' + # Gulpfiles and tests use dev dependencies + import/no-extraneous-dependencies: ['error', { devDependencies: ['gulpfile.babel.js', 'gulp/**/**.js', 'test/**/**.js']}] diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 26bede8..1560c79 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -15,23 +15,19 @@ Please include the following in your new issue: See [here](https://github.com/jiahaog/nativefier#development) for instructions on how to set up a development environment. -Follow the current code style, and make sure tests and lints pass before submitting with the following commands: +We follow the [Airbnb Style Guide](https://github.com/airbnb/javascript), please make sure tests and lints pass when you submit your pull request. -Run the following command before submitting the pull request: +The following commands might be helpful: ```bash -# Run tests and linting -$ npm run ci -``` +# Run specs and lint +npm run ci -Or you can run them separately: +# Run specs only +npm run test -```bash -# Tests -$ npm run test - -# Lint source files -$ npm run lint +# Run linter only +npm run lint ``` Thank you so much for your contribution! diff --git a/app/.eslintrc.yml b/app/.eslintrc.yml new file mode 100644 index 0000000..d4c13bf --- /dev/null +++ b/app/.eslintrc.yml @@ -0,0 +1,2 @@ +settings: + import/core-modules: [ electron ] diff --git a/app/src/components/contextMenu/contextMenu.js b/app/src/components/contextMenu/contextMenu.js index d731277..2538e63 100644 --- a/app/src/components/contextMenu/contextMenu.js +++ b/app/src/components/contextMenu/contextMenu.js @@ -1,47 +1,48 @@ -import {Menu, ipcMain, shell, clipboard, BrowserWindow} from 'electron'; +// Because we are changing the properties of `mainWindow` in initContextMenu() +/* eslint-disable no-param-reassign */ +import { Menu, ipcMain, shell, clipboard, BrowserWindow } from 'electron'; function initContextMenu(mainWindow) { - ipcMain.on('contextMenuOpened', (event, targetHref) => { - const contextMenuTemplate = [ - { - label: 'Open with default browser', - click: () => { - if (targetHref) { - shell.openExternal(targetHref); - return; - } - } - }, - { - label: 'Open in new window', - click: () => { - if (targetHref) { - new BrowserWindow().loadURL(targetHref); - return; - } + ipcMain.on('contextMenuOpened', (event, targetHref) => { + const contextMenuTemplate = [ + { + label: 'Open with default browser', + click: () => { + if (targetHref) { + shell.openExternal(targetHref); + } + }, + }, + { + label: 'Open in new window', + click: () => { + if (targetHref) { + new BrowserWindow().loadURL(targetHref); + return; + } - mainWindow.useDefaultWindowBehaviour = true; - mainWindow.webContents.send('contextMenuClosed'); - } - }, - { - label: 'Copy link location', - click: () => { - if (targetHref) { - clipboard.writeText(targetHref); - return; - } + mainWindow.useDefaultWindowBehaviour = true; + mainWindow.webContents.send('contextMenuClosed'); + }, + }, + { + label: 'Copy link location', + click: () => { + if (targetHref) { + clipboard.writeText(targetHref); + return; + } - mainWindow.useDefaultWindowBehaviour = true; - mainWindow.webContents.send('contextMenuClosed'); - } - } - ]; + mainWindow.useDefaultWindowBehaviour = true; + mainWindow.webContents.send('contextMenuClosed'); + }, + }, + ]; - const contextMenu = Menu.buildFromTemplate(contextMenuTemplate); - contextMenu.popup(mainWindow); - mainWindow.contextMenuOpen = true; - }); + const contextMenu = Menu.buildFromTemplate(contextMenuTemplate); + contextMenu.popup(mainWindow); + mainWindow.contextMenuOpen = true; + }); } export default initContextMenu; diff --git a/app/src/components/login/loginWindow.js b/app/src/components/login/loginWindow.js index 3427b01..12f7e1f 100644 --- a/app/src/components/login/loginWindow.js +++ b/app/src/components/login/loginWindow.js @@ -1,20 +1,20 @@ -import {BrowserWindow, ipcMain} from 'electron'; +import { BrowserWindow, ipcMain } from 'electron'; import path from 'path'; function createLoginWindow(loginCallback) { - var loginWindow = new BrowserWindow({ - width: 300, - height: 400, - frame: false, - resizable: false - }); - loginWindow.loadURL('file://' + path.join(__dirname, '/static/login/login.html')); + const loginWindow = new BrowserWindow({ + width: 300, + height: 400, + frame: false, + resizable: false, + }); + loginWindow.loadURL(`file://${path.join(__dirname, '/static/login/login.html')}`); - ipcMain.once('login-message', function(event, usernameAndPassword) { - loginCallback(usernameAndPassword[0], usernameAndPassword[1]); - loginWindow.close(); - }); - return loginWindow; + ipcMain.once('login-message', (event, usernameAndPassword) => { + loginCallback(usernameAndPassword[0], usernameAndPassword[1]); + loginWindow.close(); + }); + return loginWindow; } export default createLoginWindow; diff --git a/app/src/components/mainWindow/mainWindow.js b/app/src/components/mainWindow/mainWindow.js index 95e4678..62b9508 100644 --- a/app/src/components/mainWindow/mainWindow.js +++ b/app/src/components/mainWindow/mainWindow.js @@ -1,232 +1,234 @@ import fs from 'fs'; import path from 'path'; -import {BrowserWindow, shell, ipcMain, dialog} from 'electron'; +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 { isOSX, linkIsInternal, getCssToInject, shouldInjectCss } = helpers; const ZOOM_INTERVAL = 0.1; +function maybeHideWindow(window, event, fastQuit) { + if (isOSX() && !fastQuit) { + // this is called when exiting from clicking the cross button on the window + 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 {{}} options AppArgs from nativefier.json + * @param {{}} inpOptions AppArgs from nativefier.json * @param {function} onAppQuit * @param {function} setDockBadge * @returns {electron.BrowserWindow} */ -function createMainWindow(options, onAppQuit, setDockBadge) { - const mainWindowState = windowStateKeeper({ - defaultWidth: options.width || 1280, - defaultHeight: options.height || 800 - }); +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 - }); + 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); + mainWindowState.manage(mainWindow); - // after first run, no longer force full screen to be true - if (options.fullScreen) { - options.fullScreen = undefined; - fs.writeFileSync(path.join(__dirname, '..', 'nativefier.json'), JSON.stringify(options)); - } + // after first run, no longer force full screen to be true + if (options.fullScreen) { + options.fullScreen = undefined; + fs.writeFileSync(path.join(__dirname, '..', 'nativefier.json'), JSON.stringify(options)); + } - // 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)); - } + // 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; + let currentZoom = options.zoom; - const onZoomIn = () => { - currentZoom += ZOOM_INTERVAL; - mainWindow.webContents.send('change-zoom', currentZoom); - }; + const onZoomIn = () => { + currentZoom += ZOOM_INTERVAL; + mainWindow.webContents.send('change-zoom', currentZoom); + }; - const onZoomOut = () => { - 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 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) { - const session = mainWindow.webContents.session; - session.clearStorageData(() => { - session.clearCache(() => { - mainWindow.loadURL(options.targetUrl); - }); - }); - } + 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; + session.clearStorageData(() => { + session.clearCache(() => { + mainWindow.loadURL(options.targetUrl); }); - }; + }); + }); + }; - const onGoBack = () => { - mainWindow.webContents.goBack(); - }; + const onGoBack = () => { + mainWindow.webContents.goBack(); + }; - const onGoForward = () => { - mainWindow.webContents.goForward(); - }; + const onGoForward = () => { + mainWindow.webContents.goForward(); + }; - const getCurrentUrl = () => { - return mainWindow.webContents.getURL(); - }; + 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: getCurrentUrl, - clearAppData: clearAppData, - disableDevTools: options.disableDevTools - }; + 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); + 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 (options.userAgent) { - mainWindow.webContents.setUserAgent(options.userAgent); + if (linkIsInternal(options.targetUrl, urlToGo, options.internalUrls)) { + return; } + event.preventDefault(); + shell.openExternal(urlToGo); + }); - maybeInjectCss(mainWindow); - mainWindow.webContents.on('did-finish-load', () => { - mainWindow.webContents.send('params', JSON.stringify(options)); - }); + mainWindow.loadURL(options.targetUrl); - 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.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); + }); - 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); - }); - - return mainWindow; + return mainWindow; } ipcMain.on('cancelNewWindowOverride', () => { - const allWindows = BrowserWindow.getAllWindows(); - allWindows.forEach(window => { - window.useDefaultWindowBehaviour = false; - }); + const allWindows = BrowserWindow.getAllWindows(); + allWindows.forEach((window) => { + // eslint-disable-next-line no-param-reassign + window.useDefaultWindowBehaviour = false; + }); }); -function maybeHideWindow(window, event, fastQuit) { - if (isOSX() && !fastQuit) { - // this is called when exiting from clicking the cross button on the window - 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); - }); -} - export default createMainWindow; diff --git a/app/src/components/menu/menu.js b/app/src/components/menu/menu.js index fd964c3..c6bcfa0 100644 --- a/app/src/components/menu/menu.js +++ b/app/src/components/menu/menu.js @@ -1,4 +1,4 @@ -import {Menu, shell, clipboard} from 'electron'; +import { Menu, shell, clipboard } from 'electron'; /** * @param nativefierVersion @@ -13,255 +13,265 @@ import {Menu, shell, clipboard} from 'electron'; * @param clearAppData * @param disableDevTools */ -function createMenu({nativefierVersion, appQuit, zoomIn, zoomOut, zoomReset, zoomBuildTimeValue, goBack, goForward, getCurrentUrl, clearAppData, disableDevTools}) { - if (Menu.getApplicationMenu()) { - return; - } - const zoomResetLabel = (zoomBuildTimeValue === 1.0) ? +function createMenu({ nativefierVersion, + appQuit, + zoomIn, + zoomOut, + zoomReset, + zoomBuildTimeValue, + goBack, + goForward, + getCurrentUrl, + clearAppData, + disableDevTools }) { + if (Menu.getApplicationMenu()) { + return; + } + const zoomResetLabel = (zoomBuildTimeValue === 1.0) ? 'Reset Zoom' : `Reset Zoom (to ${zoomBuildTimeValue * 100}%, set at build time)`; - const template = [ + const template = [ + { + label: 'Edit', + submenu: [ { - label: 'Edit', - submenu: [ - { - label: 'Undo', - accelerator: 'CmdOrCtrl+Z', - role: 'undo' - }, - { - label: 'Redo', - accelerator: 'Shift+CmdOrCtrl+Z', - role: 'redo' - }, - { - type: 'separator' - }, - { - label: 'Cut', - accelerator: 'CmdOrCtrl+X', - role: 'cut' - }, - { - label: 'Copy', - accelerator: 'CmdOrCtrl+C', - role: 'copy' - }, - { - label: 'Copy Current URL', - accelerator: 'CmdOrCtrl+L', - click: () => { - const currentURL = getCurrentUrl(); - clipboard.writeText(currentURL); - } - }, - { - label: 'Paste', - accelerator: 'CmdOrCtrl+V', - role: 'paste' - }, - { - label: 'Select All', - accelerator: 'CmdOrCtrl+A', - role: 'selectall' - }, - { - label: 'Clear App Data', - click: () => { - clearAppData(); - } - } - ] + label: 'Undo', + accelerator: 'CmdOrCtrl+Z', + role: 'undo', }, { - label: 'View', - submenu: [ - { - label: 'Back', - accelerator: 'CmdOrCtrl+[', - click: () => { - goBack(); - } - }, - { - label: 'Forward', - accelerator: 'CmdOrCtrl+]', - click: () => { - goForward(); - } - }, - { - label: 'Reload', - accelerator: 'CmdOrCtrl+R', - click: (item, focusedWindow) => { - if (focusedWindow) { - focusedWindow.reload(); - } - } - }, - { - type: 'separator' - }, - { - label: 'Toggle Full Screen', - accelerator: (() => { - if (process.platform === 'darwin') { - return 'Ctrl+Command+F'; - } - return 'F11'; - })(), - click: (item, focusedWindow) => { - if (focusedWindow) { - focusedWindow.setFullScreen(!focusedWindow.isFullScreen()); - } - } - }, - { - label: 'Zoom In', - accelerator: (() => { - if (process.platform === 'darwin') { - return 'Command+='; - } - return 'Ctrl+='; - })(), - click: () => { - zoomIn(); - } - }, - { - label: 'Zoom Out', - accelerator: (() => { - if (process.platform === 'darwin') { - return 'Command+-'; - } - return 'Ctrl+-'; - })(), - click: () => { - zoomOut(); - } - }, - { - label: zoomResetLabel, - accelerator: (() => { - if (process.platform === 'darwin') { - return 'Command+0'; - } - return 'Ctrl+0'; - })(), - click: () => { - zoomReset(); - } - }, - { - label: 'Toggle Developer Tools', - accelerator: (() => { - if (process.platform === 'darwin') { - return 'Alt+Command+I'; - } - return 'Ctrl+Shift+I'; - })(), - click: (item, focusedWindow) => { - if (focusedWindow) { - focusedWindow.toggleDevTools(); - } - } - } - ] + label: 'Redo', + accelerator: 'Shift+CmdOrCtrl+Z', + role: 'redo', }, { - label: 'Window', - role: 'window', - submenu: [ - { - label: 'Minimize', - accelerator: 'CmdOrCtrl+M', - role: 'minimize' - }, - { - label: 'Close', - accelerator: 'CmdOrCtrl+W', - role: 'close' - } - ] + type: 'separator', }, { - label: 'Help', - role: 'help', - submenu: [ - { - label: `Built with Nativefier v${nativefierVersion}`, - click: () => { - shell.openExternal('https://github.com/jiahaog/nativefier'); - } - }, - { - label: 'Report an Issue', - click: () => { - shell.openExternal('https://github.com/jiahaog/nativefier/issues'); - } - } - ] - } - ]; - - if (disableDevTools) { - // remove last item (dev tools) from menu > view - const submenu = template[1].submenu; - submenu.splice(submenu.length - 1, 1); - } - - if (process.platform === 'darwin') { - template.unshift({ - label: 'Electron', - submenu: [ - { - label: 'Services', - role: 'services', - submenu: [] - }, - { - type: 'separator' - }, - { - label: 'Hide App', - accelerator: 'Command+H', - role: 'hide' - }, - { - label: 'Hide Others', - accelerator: 'Command+Shift+H', - role: 'hideothers' - }, - { - label: 'Show All', - role: 'unhide' - }, - { - type: 'separator' - }, - { - label: 'Quit', - accelerator: 'Command+Q', - click: () => { - appQuit(); - } - } - ] - }); - template[3].submenu.push( - { - type: 'separator' - }, - { - label: 'Bring All to Front', - role: 'front' + label: 'Cut', + accelerator: 'CmdOrCtrl+X', + role: 'cut', + }, + { + label: 'Copy', + accelerator: 'CmdOrCtrl+C', + role: 'copy', + }, + { + label: 'Copy Current URL', + accelerator: 'CmdOrCtrl+L', + click: () => { + const currentURL = getCurrentUrl(); + clipboard.writeText(currentURL); + }, + }, + { + label: 'Paste', + accelerator: 'CmdOrCtrl+V', + role: 'paste', + }, + { + label: 'Select All', + accelerator: 'CmdOrCtrl+A', + role: 'selectall', + }, + { + label: 'Clear App Data', + click: () => { + clearAppData(); + }, + }, + ], + }, + { + label: 'View', + submenu: [ + { + label: 'Back', + accelerator: 'CmdOrCtrl+[', + click: () => { + goBack(); + }, + }, + { + label: 'Forward', + accelerator: 'CmdOrCtrl+]', + click: () => { + goForward(); + }, + }, + { + label: 'Reload', + accelerator: 'CmdOrCtrl+R', + click: (item, focusedWindow) => { + if (focusedWindow) { + focusedWindow.reload(); } - ); - } + }, + }, + { + type: 'separator', + }, + { + label: 'Toggle Full Screen', + accelerator: (() => { + if (process.platform === 'darwin') { + return 'Ctrl+Command+F'; + } + return 'F11'; + })(), + click: (item, focusedWindow) => { + if (focusedWindow) { + focusedWindow.setFullScreen(!focusedWindow.isFullScreen()); + } + }, + }, + { + label: 'Zoom In', + accelerator: (() => { + if (process.platform === 'darwin') { + return 'Command+='; + } + return 'Ctrl+='; + })(), + click: () => { + zoomIn(); + }, + }, + { + label: 'Zoom Out', + accelerator: (() => { + if (process.platform === 'darwin') { + return 'Command+-'; + } + return 'Ctrl+-'; + })(), + click: () => { + zoomOut(); + }, + }, + { + label: zoomResetLabel, + accelerator: (() => { + if (process.platform === 'darwin') { + return 'Command+0'; + } + return 'Ctrl+0'; + })(), + click: () => { + zoomReset(); + }, + }, + { + label: 'Toggle Developer Tools', + accelerator: (() => { + if (process.platform === 'darwin') { + return 'Alt+Command+I'; + } + return 'Ctrl+Shift+I'; + })(), + click: (item, focusedWindow) => { + if (focusedWindow) { + focusedWindow.toggleDevTools(); + } + }, + }, + ], + }, + { + label: 'Window', + role: 'window', + submenu: [ + { + label: 'Minimize', + accelerator: 'CmdOrCtrl+M', + role: 'minimize', + }, + { + label: 'Close', + accelerator: 'CmdOrCtrl+W', + role: 'close', + }, + ], + }, + { + label: 'Help', + role: 'help', + submenu: [ + { + label: `Built with Nativefier v${nativefierVersion}`, + click: () => { + shell.openExternal('https://github.com/jiahaog/nativefier'); + }, + }, + { + label: 'Report an Issue', + click: () => { + shell.openExternal('https://github.com/jiahaog/nativefier/issues'); + }, + }, + ], + }, + ]; - const menu = Menu.buildFromTemplate(template); - Menu.setApplicationMenu(menu); + if (disableDevTools) { + // remove last item (dev tools) from menu > view + const submenu = template[1].submenu; + submenu.splice(submenu.length - 1, 1); + } + + if (process.platform === 'darwin') { + template.unshift({ + label: 'Electron', + submenu: [ + { + label: 'Services', + role: 'services', + submenu: [], + }, + { + type: 'separator', + }, + { + label: 'Hide App', + accelerator: 'Command+H', + role: 'hide', + }, + { + label: 'Hide Others', + accelerator: 'Command+Shift+H', + role: 'hideothers', + }, + { + label: 'Show All', + role: 'unhide', + }, + { + type: 'separator', + }, + { + label: 'Quit', + accelerator: 'Command+Q', + click: () => { + appQuit(); + }, + }, + ], + }); + template[3].submenu.push( + { + type: 'separator', + }, + { + label: 'Bring All to Front', + role: 'front', + }, + ); + } + + const menu = Menu.buildFromTemplate(template); + Menu.setApplicationMenu(menu); } export default createMenu; diff --git a/app/src/helpers/helpers.js b/app/src/helpers/helpers.js index aed6acf..c908a01 100644 --- a/app/src/helpers/helpers.js +++ b/app/src/helpers/helpers.js @@ -6,39 +6,39 @@ import path from 'path'; const INJECT_CSS_PATH = path.join(__dirname, '..', 'inject/inject.css'); function isOSX() { - return os.platform() === 'darwin'; + return os.platform() === 'darwin'; } function isLinux() { - return os.platform() === 'linux'; + return os.platform() === 'linux'; } function isWindows() { - return os.platform() === 'win32'; + return os.platform() === 'win32'; } function linkIsInternal(currentUrl, newUrl, internalUrlRegex) { - if (internalUrlRegex) { - var regex = RegExp(internalUrlRegex); - return regex.test(newUrl); - } + if (internalUrlRegex) { + const regex = RegExp(internalUrlRegex); + return regex.test(newUrl); + } - var currentDomain = wurl('domain', currentUrl); - var newDomain = wurl('domain', newUrl); - return currentDomain === newDomain; + const currentDomain = wurl('domain', currentUrl); + const newDomain = wurl('domain', newUrl); + return currentDomain === newDomain; } function shouldInjectCss() { - try { - fs.accessSync(INJECT_CSS_PATH, fs.F_OK); - return true; - } catch (e) { - return false; - } + try { + fs.accessSync(INJECT_CSS_PATH, fs.F_OK); + return true; + } catch (e) { + return false; + } } function getCssToInject() { - return fs.readFileSync(INJECT_CSS_PATH).toString(); + return fs.readFileSync(INJECT_CSS_PATH).toString(); } /** @@ -47,19 +47,20 @@ function getCssToInject() { * @param message */ function debugLog(browserWindow, message) { - // need the timeout as it takes time for the preload javascript to be loaded in the window - setTimeout(() => { - browserWindow.webContents.send('debug', message); - }, 3000); - console.log(message); + // need the timeout as it takes time for the preload javascript to be loaded in the window + setTimeout(() => { + browserWindow.webContents.send('debug', message); + }, 3000); + // eslint-disable-next-line no-console + console.log(message); } export default { - isOSX, - isLinux, - isWindows, - linkIsInternal, - getCssToInject, - debugLog, - shouldInjectCss + isOSX, + isLinux, + isWindows, + linkIsInternal, + getCssToInject, + debugLog, + shouldInjectCss, }; diff --git a/app/src/helpers/inferFlash.js b/app/src/helpers/inferFlash.js index 5c13360..8b555f5 100644 --- a/app/src/helpers/inferFlash.js +++ b/app/src/helpers/inferFlash.js @@ -2,23 +2,7 @@ import fs from 'fs'; import path from 'path'; import helpers from './helpers'; -const {isOSX, isWindows, isLinux} = helpers; - -function inferFlash() { - if (isOSX()) { - return darwinMatch(); - } - - if (isWindows()) { - return windowsMatch(); - } - - if (isLinux()) { - return linuxMatch(); - } - - console.warn('Unable to determine OS to infer flash player'); -} +const { isOSX, isWindows, isLinux } = helpers; /** * Synchronously find a file or directory @@ -27,56 +11,72 @@ function inferFlash() { * @param {boolean} [findDir] if true, search results will be limited to only directories * @returns {Array} */ -function findSync(pattern, base, findDir) { - const matches = []; +function findSync(pattern, basePath, findDir) { + const matches = []; - (function findSyncRecurse(base) { - let children; - try { - children = fs.readdirSync(base); - } catch (exception) { - if (exception.code === 'ENOENT') { - return; - } - throw exception; + (function findSyncRecurse(base) { + let children; + try { + children = fs.readdirSync(base); + } catch (exception) { + if (exception.code === 'ENOENT') { + return; + } + throw exception; + } + + children.forEach((child) => { + const childPath = path.join(base, child); + const childIsDirectory = fs.lstatSync(childPath).isDirectory(); + const patternMatches = pattern.test(childPath); + + if (!patternMatches) { + if (!childIsDirectory) { + return; } + findSyncRecurse(childPath); + return; + } - children.forEach(child => { - const childPath = path.join(base, child); - const childIsDirectory = fs.lstatSync(childPath).isDirectory(); - const patternMatches = pattern.test(childPath); + if (!findDir) { + matches.push(childPath); + return; + } - if (!patternMatches) { - if (!childIsDirectory) { - return; - } - findSyncRecurse(childPath); - return; - } - - if (!findDir) { - matches.push(childPath); - return; - } - - if (childIsDirectory) { - matches.push(childPath); - } - }); - })(base); - return matches; + if (childIsDirectory) { + matches.push(childPath); + } + }); + }(basePath)); + return matches; } function linuxMatch() { - return findSync(/libpepflashplayer\.so/, '/opt/google/chrome')[0]; + return findSync(/libpepflashplayer\.so/, '/opt/google/chrome')[0]; } function windowsMatch() { - return findSync(/pepflashplayer\.dll/, 'C:\\Program Files (x86)\\Google\\Chrome')[0]; + return findSync(/pepflashplayer\.dll/, 'C:\\Program Files (x86)\\Google\\Chrome')[0]; } function darwinMatch() { - return findSync(/PepperFlashPlayer.plugin/, '/Applications/Google Chrome.app/', true)[0]; + return findSync(/PepperFlashPlayer.plugin/, '/Applications/Google Chrome.app/', true)[0]; } +function inferFlash() { + if (isOSX()) { + return darwinMatch(); + } + + if (isWindows()) { + return windowsMatch(); + } + + if (isLinux()) { + return linuxMatch(); + } + + console.warn('Unable to determine OS to infer flash player'); + return null; +} export default inferFlash; diff --git a/app/src/main.js b/app/src/main.js index 1deb91d..1189069 100644 --- a/app/src/main.js +++ b/app/src/main.js @@ -1,14 +1,15 @@ import 'source-map-support/register'; import fs from 'fs'; import path from 'path'; -import {app, crashReporter} from 'electron'; +import { app, crashReporter } from 'electron'; +import electronDownload from 'electron-dl'; + import createLoginWindow from './components/login/loginWindow'; import createMainWindow from './components/mainWindow/mainWindow'; import helpers from './helpers/helpers'; import inferFlash from './helpers/inferFlash'; -import electronDownload from 'electron-dl'; -const {isOSX} = helpers; +const { isOSX } = helpers; electronDownload(); @@ -18,83 +19,82 @@ const appArgs = JSON.parse(fs.readFileSync(APP_ARGS_FILE_PATH, 'utf8')); let mainWindow; if (typeof appArgs.flashPluginDir === 'string') { - app.commandLine.appendSwitch('ppapi-flash-path', appArgs.flashPluginDir); + app.commandLine.appendSwitch('ppapi-flash-path', appArgs.flashPluginDir); } else if (appArgs.flashPluginDir) { - const flashPath = inferFlash(); - app.commandLine.appendSwitch('ppapi-flash-path', flashPath); + const flashPath = inferFlash(); + app.commandLine.appendSwitch('ppapi-flash-path', flashPath); } if (appArgs.ignoreCertificate) { - app.commandLine.appendSwitch('ignore-certificate-errors'); + app.commandLine.appendSwitch('ignore-certificate-errors'); } // do nothing for setDockBadge if not OSX let setDockBadge = () => {}; if (isOSX()) { - setDockBadge = app.dock.setBadge; + setDockBadge = app.dock.setBadge; } app.on('window-all-closed', () => { - if (!isOSX() || appArgs.fastQuit) { - app.quit(); - } + if (!isOSX() || appArgs.fastQuit) { + app.quit(); + } }); app.on('activate', (event, hasVisibleWindows) => { - if (isOSX()) { + if (isOSX()) { // this is called when the dock is clicked - if (!hasVisibleWindows) { - mainWindow.show(); - } + if (!hasVisibleWindows) { + mainWindow.show(); } + } }); app.on('before-quit', () => { - // not fired when the close button on the window is clicked - if (isOSX()) { - // need to force a quit as a workaround here to simulate the osx app hiding behaviour - // Somehow sokution at https://github.com/atom/electron/issues/444#issuecomment-76492576 does not work, - // e.prevent default appears to persist + // not fired when the close button on the window is clicked + if (isOSX()) { + // need to force a quit as a workaround here to simulate the osx app hiding behaviour + // Somehow sokution at https://github.com/atom/electron/issues/444#issuecomment-76492576 does not work, + // e.prevent default appears to persist - // might cause issues in the future as before-quit and will-quit events are not called - app.exit(0); - } + // might cause issues in the future as before-quit and will-quit events are not called + app.exit(0); + } }); if (appArgs.crashReporter) { - app.on('will-finish-launching', () => { - crashReporter.start({ - productName: appArgs.name, - submitURL: appArgs.crashReporter, - autoSubmit: true - }); + app.on('will-finish-launching', () => { + crashReporter.start({ + productName: appArgs.name, + submitURL: appArgs.crashReporter, + autoSubmit: true, }); + }); } app.on('ready', () => { - mainWindow = createMainWindow(appArgs, app.quit, setDockBadge); + mainWindow = createMainWindow(appArgs, app.quit, setDockBadge); }); app.on('login', (event, webContents, request, authInfo, callback) => { // for http authentication - event.preventDefault(); - createLoginWindow(callback); + event.preventDefault(); + createLoginWindow(callback); }); if (appArgs.singleInstance) { - const shouldQuit = app.makeSingleInstance(() => { - // Someone tried to run a second instance, we should focus our window. - if (mainWindow) { - if (mainWindow.isMinimized()) { - mainWindow.restore(); - } - mainWindow.focus(); - - } - }); - - if (shouldQuit) { - app.quit(); + const shouldQuit = app.makeSingleInstance(() => { + // Someone tried to run a second instance, we should focus our window. + if (mainWindow) { + if (mainWindow.isMinimized()) { + mainWindow.restore(); + } + mainWindow.focus(); } + }); + + if (shouldQuit) { + app.quit(); + } } diff --git a/app/src/static/.eslintrc.yml b/app/src/static/.eslintrc.yml new file mode 100644 index 0000000..5c9fc4c --- /dev/null +++ b/app/src/static/.eslintrc.yml @@ -0,0 +1,2 @@ +env: + browser: true diff --git a/app/src/static/login/login.js b/app/src/static/login/login.js index 78fbb67..2e35dfc 100644 --- a/app/src/static/login/login.js +++ b/app/src/static/login/login.js @@ -1,11 +1,12 @@ import electron from 'electron'; -const {ipcRenderer} = electron; + +const { ipcRenderer } = electron; const form = document.getElementById('login-form'); -form.addEventListener('submit', event => { - event.preventDefault(); - const username = document.getElementById('username-input').value; - const password = document.getElementById('password-input').value; - ipcRenderer.send('login-message', [username, password]); +form.addEventListener('submit', (event) => { + event.preventDefault(); + const username = document.getElementById('username-input').value; + const password = document.getElementById('password-input').value; + ipcRenderer.send('login-message', [username, password]); }); diff --git a/app/src/static/preload.js b/app/src/static/preload.js index a7d5ea5..09db72e 100644 --- a/app/src/static/preload.js +++ b/app/src/static/preload.js @@ -1,86 +1,85 @@ /** Preload file that will be executed in the renderer process */ -import {ipcRenderer, webFrame} from 'electron'; +import { ipcRenderer, webFrame } from 'electron'; import path from 'path'; import fs from 'fs'; const INJECT_JS_PATH = path.join(__dirname, '../../', 'inject/inject.js'); -setNotificationCallback((title, opt) => { - ipcRenderer.send('notification', title, opt); -}); - -document.addEventListener('DOMContentLoaded', () => { - // do things - - window.addEventListener('contextmenu', event => { - event.preventDefault(); - let targetElement = event.srcElement; - - // the clicked element is the deepest in the DOM, and may not be the bearing the href - // for example, Google - while (!targetElement.href && targetElement.parentElement) { - targetElement = targetElement.parentElement; - } - const targetHref = targetElement.href; - - if (!targetHref) { - ipcRenderer.once('contextMenuClosed', () => { - clickSelector(event.target); - ipcRenderer.send('cancelNewWindowOverride'); - }); - } - - ipcRenderer.send('contextMenuOpened', targetHref); - }, false); - - injectScripts(); -}); - -ipcRenderer.on('params', (event, message) => { - const appArgs = JSON.parse(message); - console.log('nativefier.json', appArgs); -}); - -ipcRenderer.on('debug', (event, message) => { - console.log('debug:', message); -}); - -ipcRenderer.on('change-zoom', (event, message) => { - webFrame.setZoomFactor(message); -}); - /** * Patches window.Notification to set a callback on a new Notification * @param callback */ function setNotificationCallback(callback) { + const OldNotify = window.Notification; + const newNotify = (title, opt) => { + callback(title, opt); + return new OldNotify(title, opt); + }; + newNotify.requestPermission = OldNotify.requestPermission.bind(OldNotify); + Object.defineProperty(newNotify, 'permission', { + get: () => OldNotify.permission, + }); - const OldNotify = window.Notification; - const newNotify = (title, opt) => { - callback(title, opt); - return new OldNotify(title, opt); - }; - newNotify.requestPermission = OldNotify.requestPermission.bind(OldNotify); - Object.defineProperty(newNotify, 'permission', { - get: () => { - return OldNotify.permission; - } - }); - - window.Notification = newNotify; + window.Notification = newNotify; } function clickSelector(element) { - const mouseEvent = new MouseEvent('click'); - element.dispatchEvent(mouseEvent); + const mouseEvent = new MouseEvent('click'); + element.dispatchEvent(mouseEvent); } function injectScripts() { - const needToInject = fs.existsSync(INJECT_JS_PATH); - if (!needToInject) { - return; - } - require(INJECT_JS_PATH); + const needToInject = fs.existsSync(INJECT_JS_PATH); + if (!needToInject) { + return; + } + // Dynamically require scripts + // eslint-disable-next-line global-require, import/no-dynamic-require + require(INJECT_JS_PATH); } + +setNotificationCallback((title, opt) => { + ipcRenderer.send('notification', title, opt); +}); + +document.addEventListener('DOMContentLoaded', () => { + window.addEventListener('contextmenu', (event) => { + event.preventDefault(); + let targetElement = event.srcElement; + + // the clicked element is the deepest in the DOM, and may not be the bearing the href + // for example, Google + while (!targetElement.href && targetElement.parentElement) { + targetElement = targetElement.parentElement; + } + const targetHref = targetElement.href; + + if (!targetHref) { + ipcRenderer.once('contextMenuClosed', () => { + clickSelector(event.target); + ipcRenderer.send('cancelNewWindowOverride'); + }); + } + + ipcRenderer.send('contextMenuOpened', targetHref); + }, false); + + injectScripts(); +}); + +ipcRenderer.on('params', (event, message) => { + const appArgs = JSON.parse(message); + console.log('nativefier.json', appArgs); +}); + +ipcRenderer.on('debug', (event, message) => { + // eslint-disable-next-line no-console + console.log('debug:', message); +}); + +ipcRenderer.on('change-zoom', (event, message) => { + webFrame.setZoomFactor(message); +}); + diff --git a/gulp/build.js b/gulp/build.js index 5bbcef3..7b832fb 100644 --- a/gulp/build.js +++ b/gulp/build.js @@ -1,19 +1,18 @@ import gulp from 'gulp'; -import PATHS from './helpers/src-paths'; - import del from 'del'; import runSequence from 'run-sequence'; +import PATHS from './helpers/src-paths'; -gulp.task('build', callback => { - runSequence('clean', ['build-cli', 'build-app', 'build-tests'], callback); +gulp.task('build', (callback) => { + runSequence('clean', ['build-cli', 'build-app', 'build-tests'], callback); }); -gulp.task('clean', callback => { - del(PATHS.CLI_DEST).then(() => { - del(PATHS.APP_DEST).then(() => { - del(PATHS.TEST_DEST).then(() => { - callback(); - }); - }); +gulp.task('clean', (callback) => { + del(PATHS.CLI_DEST).then(() => { + del(PATHS.APP_DEST).then(() => { + del(PATHS.TEST_DEST).then(() => { + callback(); + }); }); + }); }); diff --git a/gulp/build/build-app.js b/gulp/build/build-app.js index 8ba2083..ab0b3e1 100644 --- a/gulp/build/build-app.js +++ b/gulp/build/build-app.js @@ -1,10 +1,9 @@ import gulp from 'gulp'; +import webpack from 'webpack-stream'; import PATHS from './../helpers/src-paths'; -import webpack from 'webpack-stream'; +const webpackConfig = require('./../../webpack.config.js'); -gulp.task('build-app', ['build-static'], () => { - return gulp.src(PATHS.APP_MAIN_JS) - .pipe(webpack(require('./../../webpack.config.js'))) - .pipe(gulp.dest(PATHS.APP_DEST)); -}); +gulp.task('build-app', ['build-static'], () => gulp.src(PATHS.APP_MAIN_JS) + .pipe(webpack(webpackConfig)) + .pipe(gulp.dest(PATHS.APP_DEST))); diff --git a/gulp/build/build-cli.js b/gulp/build/build-cli.js index 0b91f6e..a3b54c5 100644 --- a/gulp/build/build-cli.js +++ b/gulp/build/build-cli.js @@ -2,8 +2,6 @@ import gulp from 'gulp'; import PATHS from './../helpers/src-paths'; import helpers from './../helpers/gulp-helpers'; -const {buildES6} = helpers; +const { buildES6 } = helpers; -gulp.task('build-cli', done => { - return buildES6(PATHS.CLI_SRC_JS, PATHS.CLI_DEST, done); -}); +gulp.task('build-cli', done => buildES6(PATHS.CLI_SRC_JS, PATHS.CLI_DEST, done)); diff --git a/gulp/build/build-static.js b/gulp/build/build-static.js index 3cba6b2..543b4a4 100644 --- a/gulp/build/build-static.js +++ b/gulp/build/build-static.js @@ -2,15 +2,11 @@ import gulp from 'gulp'; import PATHS from './../helpers/src-paths'; import helpers from './../helpers/gulp-helpers'; -const {buildES6} = helpers; +const { buildES6 } = helpers; -gulp.task('build-static-not-js', () => { - return gulp.src([PATHS.APP_STATIC_ALL, '!**/*.js']) - .pipe(gulp.dest(PATHS.APP_STATIC_DEST)); -}); +gulp.task('build-static-not-js', () => gulp.src([PATHS.APP_STATIC_ALL, '!**/*.js']) + .pipe(gulp.dest(PATHS.APP_STATIC_DEST))); -gulp.task('build-static-js', done => { - return buildES6(PATHS.APP_STATIC_JS, PATHS.APP_STATIC_DEST, done); -}); +gulp.task('build-static-js', done => buildES6(PATHS.APP_STATIC_JS, PATHS.APP_STATIC_DEST, done)); gulp.task('build-static', ['build-static-js', 'build-static-not-js']); diff --git a/gulp/helpers/gulp-helpers.js b/gulp/helpers/gulp-helpers.js index 36bd3e1..432a5ba 100644 --- a/gulp/helpers/gulp-helpers.js +++ b/gulp/helpers/gulp-helpers.js @@ -4,17 +4,17 @@ import sourcemaps from 'gulp-sourcemaps'; import babel from 'gulp-babel'; function shellExec(cmd, silent, callback) { - shellJs.exec(cmd, {silent: silent}, (code, stdout, stderr) => { - if (code) { - callback(JSON.stringify({code, stdout, stderr})); - return; - } - callback(); - }); + shellJs.exec(cmd, { silent }, (code, stdout, stderr) => { + if (code) { + callback(JSON.stringify({ code, stdout, stderr })); + return; + } + callback(); + }); } function buildES6(src, dest, callback) { - return gulp.src(src) + return gulp.src(src) .pipe(sourcemaps.init()) .pipe(babel()) .on('error', callback) @@ -23,6 +23,6 @@ function buildES6(src, dest, callback) { } export default { - shellExec, - buildES6 + shellExec, + buildES6, }; diff --git a/gulp/helpers/src-paths.js b/gulp/helpers/src-paths.js index 933716b..512f64f 100644 --- a/gulp/helpers/src-paths.js +++ b/gulp/helpers/src-paths.js @@ -1,22 +1,22 @@ import path from 'path'; const paths = { - APP_SRC: 'app/src', - APP_DEST: 'app/lib', - CLI_SRC: 'src', - CLI_DEST: 'lib', - TEST_SRC: 'test', - TEST_DEST: 'built-tests' + APP_SRC: 'app/src', + APP_DEST: 'app/lib', + CLI_SRC: 'src', + CLI_DEST: 'lib', + TEST_SRC: 'test', + TEST_DEST: 'built-tests', }; paths.APP_MAIN_JS = path.join(paths.APP_SRC, '/main.js'); -paths.APP_ALL = paths.APP_SRC + '/**/*'; -paths.APP_STATIC_ALL = path.join(paths.APP_SRC, 'static') + '/**/*'; -paths.APP_STATIC_JS = path.join(paths.APP_SRC, 'static') + '/**/*.js'; +paths.APP_ALL = `${paths.APP_SRC}/**/*`; +paths.APP_STATIC_ALL = `${path.join(paths.APP_SRC, 'static')}/**/*`; +paths.APP_STATIC_JS = `${path.join(paths.APP_SRC, 'static')}/**/*.js`; paths.APP_STATIC_DEST = path.join(paths.APP_DEST, 'static'); -paths.CLI_SRC_JS = paths.CLI_SRC + '/**/*.js'; -paths.CLI_DEST_JS = paths.CLI_DEST + '/**/*.js'; -paths.TEST_SRC_JS = paths.TEST_SRC + '/**/*.js'; -paths.TEST_DEST_JS = paths.TEST_DEST + '/**/*.js'; +paths.CLI_SRC_JS = `${paths.CLI_SRC}/**/*.js`; +paths.CLI_DEST_JS = `${paths.CLI_DEST}/**/*.js`; +paths.TEST_SRC_JS = `${paths.TEST_SRC}/**/*.js`; +paths.TEST_DEST_JS = `${paths.TEST_DEST}/**/*.js`; export default paths; diff --git a/gulp/release.js b/gulp/release.js index 3d0e386..4e7dd98 100644 --- a/gulp/release.js +++ b/gulp/release.js @@ -2,12 +2,10 @@ import gulp from 'gulp'; import runSequence from 'run-sequence'; import helpers from './helpers/gulp-helpers'; -const {shellExec} = helpers; +const { shellExec } = helpers; -gulp.task('publish', done => { - shellExec('npm publish', false, done); +gulp.task('publish', (done) => { + shellExec('npm publish', false, done); }); -gulp.task('release', callback => { - return runSequence('build', 'publish', callback); -}); +gulp.task('release', callback => runSequence('build', 'publish', callback)); diff --git a/gulp/test.js b/gulp/test.js index a0dcd42..e52f50a 100644 --- a/gulp/test.js +++ b/gulp/test.js @@ -2,12 +2,10 @@ import gulp from 'gulp'; import runSequence from 'run-sequence'; import helpers from './helpers/gulp-helpers'; -const {shellExec} = helpers; +const { shellExec } = helpers; -gulp.task('prune', done => { - shellExec('npm prune', true, done); +gulp.task('prune', (done) => { + shellExec('npm prune', true, done); }); -gulp.task('test', callback => { - return runSequence('prune', 'mocha', callback); -}); +gulp.task('test', callback => runSequence('prune', 'mocha', callback)); diff --git a/gulp/tests/build-tests.js b/gulp/tests/build-tests.js index 6d5b1b8..98786e8 100644 --- a/gulp/tests/build-tests.js +++ b/gulp/tests/build-tests.js @@ -2,8 +2,6 @@ import gulp from 'gulp'; import PATHS from './../helpers/src-paths'; import helpers from './../helpers/gulp-helpers'; -const {buildES6} = helpers; +const { buildES6 } = helpers; -gulp.task('build-tests', done => { - return buildES6(PATHS.TEST_SRC_JS, PATHS.TEST_DEST, done); -}); +gulp.task('build-tests', done => buildES6(PATHS.TEST_SRC_JS, PATHS.TEST_DEST, done)); diff --git a/gulp/tests/mocha.js b/gulp/tests/mocha.js index bb67128..8aba174 100644 --- a/gulp/tests/mocha.js +++ b/gulp/tests/mocha.js @@ -1,32 +1,27 @@ import gulp from 'gulp'; +import istanbul from 'gulp-istanbul'; +import { Instrumenter } from 'isparta'; +import mocha from 'gulp-mocha'; import PATHS from './../helpers/src-paths'; -import istanbul from 'gulp-istanbul'; -import {Instrumenter} from 'isparta'; -import mocha from 'gulp-mocha'; - -gulp.task('mocha', done => { - gulp.src([PATHS.CLI_SRC_JS, '!src/cli.js']) - .pipe(istanbul({ - instrumenter: Instrumenter, - includeUntested: true - })) - .pipe(istanbul.hookRequire()) // Force `require` to return covered files - .on('finish', () => { - return gulp.src(PATHS.TEST_SRC, {read: false}) - .pipe(mocha({ - compilers: 'js:babel-core/register', - recursive: true - })) - .pipe(istanbul.writeReports({ - dir: './coverage', - reporters: ['lcov'], - reportOpts: {dir: './coverage'} - })) - .on('end', done); - }); +gulp.task('mocha', (done) => { + gulp.src([PATHS.CLI_SRC_JS, '!src/cli.js']) + .pipe(istanbul({ + instrumenter: Instrumenter, + includeUntested: true, + })) + .pipe(istanbul.hookRequire()) // Force `require` to return covered files + .on('finish', () => gulp.src(PATHS.TEST_SRC, { read: false }) + .pipe(mocha({ + compilers: 'js:babel-core/register', + recursive: true, + })) + .pipe(istanbul.writeReports({ + dir: './coverage', + reporters: ['lcov'], + reportOpts: { dir: './coverage' }, + })) + .on('end', done)); }); -gulp.task('tdd', ['mocha'], () => { - return gulp.watch(['src/**/*.js', 'test/**/*.js'], ['mocha']); -}); +gulp.task('tdd', ['mocha'], () => gulp.watch(['src/**/*.js', 'test/**/*.js'], ['mocha'])); diff --git a/gulp/watch.js b/gulp/watch.js index c1d5433..9bc325d 100644 --- a/gulp/watch.js +++ b/gulp/watch.js @@ -2,15 +2,15 @@ import gulp from 'gulp'; import PATHS from './helpers/src-paths'; gulp.task('watch', ['build'], () => { - var handleError = function(error) { - console.error(error); - }; - gulp.watch(PATHS.APP_ALL, ['build-app']) + const handleError = function (error) { + console.error(error); + }; + gulp.watch(PATHS.APP_ALL, ['build-app']) .on('error', handleError); - gulp.watch(PATHS.CLI_SRC_JS, ['build-cli']) + gulp.watch(PATHS.CLI_SRC_JS, ['build-cli']) .on('error', handleError); - gulp.watch(PATHS.TEST_SRC_JS, ['build-tests']) + gulp.watch(PATHS.TEST_SRC_JS, ['build-tests']) .on('error', handleError); }); diff --git a/gulpfile.babel.js b/gulpfile.babel.js index 912b3d4..0cfb1c6 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -2,8 +2,8 @@ import gulp from 'gulp'; import requireDir from 'require-dir'; requireDir('./gulp', { - recurse: true, - duplicates: true + recurse: true, + duplicates: true, }); gulp.task('default', ['build']); diff --git a/package.json b/package.json index aa92937..04b7f0d 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "test": "gulp test", "tdd": "gulp tdd", "lint": "eslint .", + "lint:fix": "eslint . --fix", "ci": "gulp build test && npm run lint", "clean": "gulp clean", "build": "gulp build", @@ -68,8 +69,9 @@ "babel-register": "^6.6.0", "chai": "^3.4.1", "del": "^2.2.0", - "eslint": "^2.10.2", - "eslint-config-google": "^0.5.0", + "eslint": "^3.19.0", + "eslint-config-airbnb-base": "^11.1.3", + "eslint-plugin-import": "^2.2.0", "gulp": "^3.9.0", "gulp-babel": "^6.1.1", "gulp-istanbul": "^1.1.1", diff --git a/src/build/buildApp.js b/src/build/buildApp.js index 9214201..d82da8a 100644 --- a/src/build/buildApp.js +++ b/src/build/buildApp.js @@ -7,8 +7,100 @@ import ncp from 'ncp'; const copy = ncp.ncp; /** - * Creates a temporary directory and copies the './app folder' inside, and adds a text file with the configuration - * for the single page app. + * Only picks certain app args to pass to nativefier.json + * @param options + */ +function selectAppArgs(options) { + return { + name: options.name, + targetUrl: options.targetUrl, + counter: options.counter, + width: options.width, + height: options.height, + minWidth: options.minWidth, + minHeight: options.minHeight, + maxWidth: options.maxWidth, + maxHeight: options.maxHeight, + showMenuBar: options.showMenuBar, + fastQuit: options.fastQuit, + userAgent: options.userAgent, + nativefierVersion: options.nativefierVersion, + ignoreCertificate: options.ignoreCertificate, + insecure: options.insecure, + flashPluginDir: options.flashPluginDir, + fullScreen: options.fullScreen, + hideWindowFrame: options.hideWindowFrame, + maximize: options.maximize, + disableContextMenu: options.disableContextMenu, + disableDevTools: options.disableDevTools, + zoom: options.zoom, + internalUrls: options.internalUrls, + crashReporter: options.crashReporter, + singleInstance: options.singleInstance, + }; +} + +function maybeCopyScripts(srcs, dest) { + if (!srcs) { + return new Promise((resolve) => { + resolve(); + }); + } + const promises = srcs.map(src => new Promise((resolve, reject) => { + if (!fs.existsSync(src)) { + reject('Error copying injection files: file not found'); + return; + } + + let destFileName; + if (path.extname(src) === '.js') { + destFileName = 'inject.js'; + } else if (path.extname(src) === '.css') { + destFileName = 'inject.css'; + } else { + resolve(); + return; + } + + copy(src, path.join(dest, 'inject', destFileName), (error) => { + if (error) { + reject(`Error Copying injection files: ${error}`); + return; + } + resolve(); + }); + })); + + return new Promise((resolve, reject) => { + Promise.all(promises) + .then(() => { + resolve(); + }) + .catch((error) => { + reject(error); + }); + }); +} + +function normalizeAppName(appName, url) { + // use a simple 3 byte random string to prevent collision + const hash = crypto.createHash('md5'); + hash.update(url); + const postFixHash = hash.digest('hex').substring(0, 6); + const normalized = _.kebabCase(appName.toLowerCase()); + return `${normalized}-nativefier-${postFixHash}`; +} + +function changeAppPackageJsonName(appPath, name, url) { + const packageJsonPath = path.join(appPath, '/package.json'); + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath)); + packageJson.name = normalizeAppName(name, url); + fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson)); +} + +/** + * Creates a temporary directory and copies the './app folder' inside, + * and adds a text file with the configuration for the single page app. * * @param {string} src * @param {string} dest @@ -16,119 +108,25 @@ const copy = ncp.ncp; * @param callback */ function buildApp(src, dest, options, callback) { - const appArgs = selectAppArgs(options); - copy(src, dest, error => { - if (error) { - callback(`Error Copying temporary directory: ${error}`); - return; - } - - fs.writeFileSync(path.join(dest, '/nativefier.json'), JSON.stringify(appArgs)); - - maybeCopyScripts(options.inject, dest) - .catch(error => { - console.warn(error); - }) - .then(() => { - changeAppPackageJsonName(dest, appArgs.name, appArgs.targetUrl); - callback(); - }); - }); -} - -function maybeCopyScripts(srcs, dest) { - if (!srcs) { - return new Promise(resolve => { - resolve(); - }); + const appArgs = selectAppArgs(options); + copy(src, dest, (error) => { + if (error) { + callback(`Error Copying temporary directory: ${error}`); + return; } - const promises = srcs.map(src => { - return new Promise((resolve, reject) => { - if (!fs.existsSync(src)) { - reject('Error copying injection files: file not found'); - return; - } - let destFileName; - if (path.extname(src) === '.js') { - destFileName = 'inject.js'; - } else if (path.extname(src) === '.css') { - destFileName = 'inject.css'; - } else { - resolve(); - return; - } + fs.writeFileSync(path.join(dest, '/nativefier.json'), JSON.stringify(appArgs)); - copy(src, path.join(dest, 'inject', destFileName), error => { - if (error) { - reject(`Error Copying injection files: ${error}`); - return; - } - resolve(); - }); - }); - }); - - return new Promise((resolve, reject) => { - Promise.all(promises) - .then(() => { - resolve(); - }) - .catch(error => { - reject(error); - }); - }); + maybeCopyScripts(options.inject, dest) + .catch((error) => { + console.warn(error); + }) + .then(() => { + changeAppPackageJsonName(dest, appArgs.name, appArgs.targetUrl); + callback(); + }); + }); } -function changeAppPackageJsonName(appPath, name, url) { - const packageJsonPath = path.join(appPath, '/package.json'); - const packageJson = JSON.parse(fs.readFileSync(packageJsonPath)); - packageJson.name = normalizeAppName(name, url); - fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson)); -} - -/** - * Only picks certain app args to pass to nativefier.json - * @param options - * @returns {{name: (*|string), targetUrl: (string|*), counter: *, width: *, height: *, showMenuBar: *, userAgent: *, nativefierVersion: *, insecure: *, disableWebSecurity: *}} - */ -function selectAppArgs(options) { - return { - name: options.name, - targetUrl: options.targetUrl, - counter: options.counter, - width: options.width, - height: options.height, - minWidth: options.minWidth, - minHeight: options.minHeight, - maxWidth: options.maxWidth, - maxHeight: options.maxHeight, - showMenuBar: options.showMenuBar, - fastQuit: options.fastQuit, - userAgent: options.userAgent, - nativefierVersion: options.nativefierVersion, - ignoreCertificate: options.ignoreCertificate, - insecure: options.insecure, - flashPluginDir: options.flashPluginDir, - fullScreen: options.fullScreen, - hideWindowFrame: options.hideWindowFrame, - maximize: options.maximize, - disableContextMenu: options.disableContextMenu, - disableDevTools: options.disableDevTools, - zoom: options.zoom, - internalUrls: options.internalUrls, - crashReporter: options.crashReporter, - singleInstance: options.singleInstance - }; -} - -function normalizeAppName(appName, url) { - // use a simple 3 byte random string to prevent collision - let hash = crypto.createHash('md5'); - hash.update(url); - const postFixHash = hash.digest('hex').substring(0, 6); - const normalized = _.kebabCase(appName.toLowerCase()); - return `${normalized}-nativefier-${postFixHash}`; -} export default buildApp; diff --git a/src/build/buildMain.js b/src/build/buildMain.js index 429b607..8b3eff8 100644 --- a/src/build/buildMain.js +++ b/src/build/buildMain.js @@ -15,118 +15,39 @@ import buildApp from './buildApp'; const copy = ncp.ncp; const isWindows = helpers.isWindows; -/** - * @callback buildAppCallback - * @param error - * @param {string} appPath - */ - -/** - * - * @param {{}} options - * @param {buildAppCallback} callback - */ -function buildMain(options, callback) { - // pre process app - - const tmpObj = tmp.dirSync({unsafeCleanup: true}); - const tmpPath = tmpObj.name; - - // todo check if this is still needed on later version of packager - const packagerConsole = new PackagerConsole(); - - const progress = new DishonestProgress(5); - - async.waterfall([ - callback => { - progress.tick('inferring'); - optionsFactory(options, callback); - }, - (options, callback) => { - progress.tick('copying'); - buildApp(options.dir, tmpPath, options, error => { - if (error) { - callback(error); - return; - } - // dir now correctly references the app folder to package - options.dir = tmpPath; - callback(null, options); - }); - }, - (options, callback) => { - progress.tick('icons'); - iconBuild(options, (error, optionsWithIcon) => { - callback(null, optionsWithIcon); - }); - }, - (options, callback) => { - progress.tick('packaging'); - // maybe skip passing icon parameter to electron packager - const packageOptions = maybeNoIconOption(options); - - packagerConsole.override(); - - packager(packageOptions, (error, appPathArray) => { - - // restore console.error - packagerConsole.restore(); - - // pass options which still contains the icon to waterfall - callback(error, options, appPathArray); - }); - }, - (options, appPathArray, callback) => { - progress.tick('finalizing'); - // somehow appPathArray is a 1 element array - const appPath = getAppPath(appPathArray); - if (!appPath) { - callback(); - return; - } - - maybeCopyIcons(options, appPath, error => { - callback(error, appPath); - }); - } - ], (error, appPath) => { - packagerConsole.playback(); - callback(error, appPath); - }); -} - /** * Checks the app path array to determine if the packaging was completed successfully * @param appPathArray Result from electron-packager * @returns {*} */ function getAppPath(appPathArray) { - if (appPathArray.length === 0) { - // directory already exists, --overwrite is not set - // exit here - return null; - } + if (appPathArray.length === 0) { + // directory already exists, --overwrite is not set + // exit here + return null; + } - if (appPathArray.length > 1) { - log.warn('Warning: This should not be happening, packaged app path contains more than one element:', appPathArray); - } + if (appPathArray.length > 1) { + log.warn('Warning: This should not be happening, packaged app path contains more than one element:', appPathArray); + } - return appPathArray[0]; + return appPathArray[0]; } /** - * Removes the `icon` parameter from options if building for Windows while not on Windows and Wine is not installed + * Removes the `icon` parameter from options if building for Windows while not on Windows + * and Wine is not installed * @param options */ function maybeNoIconOption(options) { - const packageOptions = JSON.parse(JSON.stringify(options)); - if (options.platform === 'win32' && !isWindows()) { - if (!hasBinary.sync('wine')) { - log.warn('Wine is required to set the icon for a Windows app when packaging on non-windows platforms'); - packageOptions.icon = null; - } + const packageOptions = JSON.parse(JSON.stringify(options)); + if (options.platform === 'win32' && !isWindows()) { + if (!hasBinary.sync('wine')) { + log.warn('Wine is required to set the icon for a Windows app when packaging on non-windows platforms'); + packageOptions.icon = null; } - return packageOptions; + } + return packageOptions; } /** @@ -137,23 +58,105 @@ function maybeNoIconOption(options) { * @param callback */ function maybeCopyIcons(options, appPath, callback) { - if (!options.icon) { + if (!options.icon) { + callback(); + return; + } + + if (options.platform === 'darwin') { + callback(); + return; + } + + // windows & linux + // put the icon file into the app + const destIconPath = path.join(appPath, 'resources/app'); + const destFileName = `icon${path.extname(options.icon)}`; + copy(options.icon, path.join(destIconPath, destFileName), (error) => { + callback(error); + }); +} + + +/** + * @callback buildAppCallback + * @param error + * @param {string} appPath + */ + +/** + * + * @param {{}} inpOptions + * @param {buildAppCallback} callback + */ +function buildMain(inpOptions, callback) { + const options = Object.assign({}, inpOptions); + + // pre process app + + const tmpObj = tmp.dirSync({ unsafeCleanup: true }); + const tmpPath = tmpObj.name; + + // todo check if this is still needed on later version of packager + const packagerConsole = new PackagerConsole(); + + const progress = new DishonestProgress(5); + + async.waterfall([ + (callback) => { + progress.tick('inferring'); + optionsFactory(options, callback); + }, + (options, callback) => { + progress.tick('copying'); + buildApp(options.dir, tmpPath, options, (error) => { + if (error) { + callback(error); + return; + } + // Change the reference file for the Electron app to be the temporary path + const newOptions = Object.assign({}, options, { dir: tmpPath }); + callback(null, newOptions); + }); + }, + (options, callback) => { + progress.tick('icons'); + iconBuild(options, (error, optionsWithIcon) => { + callback(null, optionsWithIcon); + }); + }, + (options, callback) => { + progress.tick('packaging'); + // maybe skip passing icon parameter to electron packager + const packageOptions = maybeNoIconOption(options); + + packagerConsole.override(); + + packager(packageOptions, (error, appPathArray) => { + // restore console.error + packagerConsole.restore(); + + // pass options which still contains the icon to waterfall + callback(error, options, appPathArray); + }); + }, + (options, appPathArray, callback) => { + progress.tick('finalizing'); + // somehow appPathArray is a 1 element array + const appPath = getAppPath(appPathArray); + if (!appPath) { callback(); return; - } + } - if (options.platform === 'darwin') { - callback(); - return; - } - - // windows & linux - // put the icon file into the app - const destIconPath = path.join(appPath, 'resources/app'); - const destFileName = `icon${path.extname(options.icon)}`; - copy(options.icon, path.join(destIconPath, destFileName), error => { - callback(error); - }); + maybeCopyIcons(options, appPath, (error) => { + callback(error, appPath); + }); + }, + ], (error, appPath) => { + packagerConsole.playback(); + callback(error, appPath); + }); } export default buildMain; diff --git a/src/build/iconBuild.js b/src/build/iconBuild.js index cc754df..dcdaa95 100644 --- a/src/build/iconBuild.js +++ b/src/build/iconBuild.js @@ -3,8 +3,20 @@ import log from 'loglevel'; import helpers from './../helpers/helpers'; import iconShellHelpers from './../helpers/iconShellHelpers'; -const {isOSX} = helpers; -const {convertToPng, convertToIco, convertToIcns} = iconShellHelpers; +const { isOSX } = helpers; +const { convertToPng, convertToIco, convertToIcns } = iconShellHelpers; + +function iconIsIco(iconPath) { + return path.extname(iconPath) === '.ico'; +} + +function iconIsPng(iconPath) { + return path.extname(iconPath) === '.png'; +} + +function iconIsIcns(iconPath) { + return path.extname(iconPath) === '.icns'; +} /** * @callback augmentIconsCallback @@ -16,88 +28,76 @@ const {convertToPng, convertToIco, convertToIcns} = iconShellHelpers; * Will check and convert a `.png` to `.icns` if necessary and augment * options.icon with the result * - * @param options will need options.platform and options.icon + * @param inpOptions will need options.platform and options.icon * @param {augmentIconsCallback} callback */ -function iconBuild(options, callback) { +function iconBuild(inpOptions, callback) { + const options = Object.assign({}, inpOptions); + const returnCallback = () => { + callback(null, options); + }; - const returnCallback = () => { - callback(null, options); - }; + if (!options.icon) { + returnCallback(); + return; + } - if (!options.icon) { + if (options.platform === 'win32') { + if (iconIsIco(options.icon)) { + returnCallback(); + return; + } + + convertToIco(options.icon) + .then((outPath) => { + options.icon = outPath; returnCallback(); - return; - } - - if (options.platform === 'win32') { - if (iconIsIco(options.icon)) { - returnCallback(); - return; - } - - convertToIco(options.icon) - .then(outPath => { - options.icon = outPath; - returnCallback(); - }) - .catch(error => { - log.warn('Skipping icon conversion to .ico', error); - returnCallback(); - }); - return; - } - - if (options.platform === 'linux') { - if (iconIsPng(options.icon)) { - returnCallback(); - return; - } - - convertToPng(options.icon) - .then(outPath => { - options.icon = outPath; - returnCallback(); - }) - .catch(error => { - log.warn('Skipping icon conversion to .png', error); - returnCallback(); - }); - return; - } - - if (iconIsIcns(options.icon)) { + }) + .catch((error) => { + log.warn('Skipping icon conversion to .ico', error); returnCallback(); - return; + }); + return; + } + + if (options.platform === 'linux') { + if (iconIsPng(options.icon)) { + returnCallback(); + return; } - if (!isOSX()) { - log.warn('Skipping icon conversion to .icns, conversion is only supported on OSX'); + convertToPng(options.icon) + .then((outPath) => { + options.icon = outPath; returnCallback(); - return; - } + }) + .catch((error) => { + log.warn('Skipping icon conversion to .png', error); + returnCallback(); + }); + return; + } - convertToIcns(options.icon) - .then(outPath => { - options.icon = outPath; - returnCallback(); - }) - .catch(error => { - log.warn('Skipping icon conversion to .icns', error); - returnCallback(); - }); -} + if (iconIsIcns(options.icon)) { + returnCallback(); + return; + } -function iconIsIco(iconPath) { - return path.extname(iconPath) === '.ico'; -} + if (!isOSX()) { + log.warn('Skipping icon conversion to .icns, conversion is only supported on OSX'); + returnCallback(); + return; + } -function iconIsPng(iconPath) { - return path.extname(iconPath) === '.png'; -} - -function iconIsIcns(iconPath) { - return path.extname(iconPath) === '.icns'; + convertToIcns(options.icon) + .then((outPath) => { + options.icon = outPath; + returnCallback(); + }) + .catch((error) => { + log.warn('Skipping icon conversion to .icns', error); + returnCallback(); + }); } export default iconBuild; diff --git a/src/cli.js b/src/cli.js index c58fdae..3441bd0 100755 --- a/src/cli.js +++ b/src/cli.js @@ -1,75 +1,73 @@ #! /usr/bin/env node import 'source-map-support/register'; - -import path from 'path'; import program from 'commander'; import nativefier from './index'; -const packageJson = require(path.join('..', 'package')); + +const packageJson = require('./../package'); function collect(val, memo) { - memo.push(val); - return memo; + memo.push(val); + return memo; } if (require.main === module) { + program + .version(packageJson.version) + .arguments(' [dest]') + .action((targetUrl, appDir) => { + program.targetUrl = targetUrl; + program.out = appDir; + }) + .option('-n, --name ', 'app name') + .option('-p, --platform ', '\'osx\', \'linux\' or \'windows\'') + .option('-a, --arch ', '\'ia32\' or \'x64\'') + .option('-e, --electron-version ', 'electron version to package, without the \'v\', see https://github.com/atom/electron/releases') + .option('--no-overwrite', 'do not override output directory if it already exists, defaults to false') + .option('-c, --conceal', 'packages the source code within your app into an archive, defaults to false, see http://electron.atom.io/docs/v0.36.0/tutorial/application-packaging/') + .option('--counter', 'if the target app should use a persistant counter badge in the dock (OSX only), defaults to false') + .option('-i, --icon ', 'the icon file to use as the icon for the app (should be a .icns file on OSX, .png for Windows and Linux)') + .option('--width ', 'set window default width, defaults to 1280px', parseInt) + .option('--height ', 'set window default height, defaults to 800px', parseInt) + .option('--min-width ', 'set window minimum width, defaults to 0px', parseInt) + .option('--min-height ', 'set window minimum height, defaults to 0px', parseInt) + .option('--max-width ', 'set window maximum width, default is no limit', parseInt) + .option('--max-height ', 'set window maximum height, default is no limit', parseInt) + .option('-m, --show-menu-bar', 'set menu bar visible, defaults to false') + .option('-f, --fast-quit', 'quit app after window close (OSX only), defaults to false') + .option('-u, --user-agent ', 'set the user agent string for the app') + .option('--honest', 'prevent the nativefied app from changing the user agent string to masquerade as a regular chrome browser') + .option('--ignore-certificate', 'ignore certificate related errors') + .option('--insecure', 'enable loading of insecure content, defaults to false') + .option('--flash', 'if flash should be enabled') + .option('--flash-path ', 'path to Chrome flash plugin, find it in `Chrome://plugins`') + .option('--inject ', 'path to a CSS/JS file to be injected', collect, []) + .option('--full-screen', 'if the app should always be started in full screen') + .option('--maximize', 'if the app should always be started maximized') + .option('--hide-window-frame', 'disable window frame and controls') + .option('--verbose', 'if verbose logs should be displayed') + .option('--disable-context-menu', 'disable the context menu') + .option('--disable-dev-tools', 'disable developer tools') + .option('--zoom ', 'default zoom factor to use when the app is opened, defaults to 1.0', parseFloat) + .option('--internal-urls ', 'regular expression of URLs to consider "internal"; all other URLs will be opened in an external browser. (default: URLs on same second-level domain as app)') + .option('--crash-reporter ', 'remote server URL to send crash reports') + .option('--single-instance', 'allow only a single instance of the application') + .parse(process.argv); - program - .version(packageJson.version) - .arguments(' [dest]') - .action(function(targetUrl, appDir) { - program.targetUrl = targetUrl; - program.out = appDir; - }) - .option('-n, --name ', 'app name') - .option('-p, --platform ', '\'osx\', \'linux\' or \'windows\'') - .option('-a, --arch ', '\'ia32\' or \'x64\'') - .option('-e, --electron-version ', 'electron version to package, without the \'v\', see https://github.com/atom/electron/releases') - .option('--no-overwrite', 'do not override output directory if it already exists, defaults to false') - .option('-c, --conceal', 'packages the source code within your app into an archive, defaults to false, see http://electron.atom.io/docs/v0.36.0/tutorial/application-packaging/') - .option('--counter', 'if the target app should use a persistant counter badge in the dock (OSX only), defaults to false') - .option('-i, --icon ', 'the icon file to use as the icon for the app (should be a .icns file on OSX, .png for Windows and Linux)') - .option('--width ', 'set window default width, defaults to 1280px', parseInt) - .option('--height ', 'set window default height, defaults to 800px', parseInt) - .option('--min-width ', 'set window minimum width, defaults to 0px', parseInt) - .option('--min-height ', 'set window minimum height, defaults to 0px', parseInt) - .option('--max-width ', 'set window maximum width, default is no limit', parseInt) - .option('--max-height ', 'set window maximum height, default is no limit', parseInt) - .option('-m, --show-menu-bar', 'set menu bar visible, defaults to false') - .option('-f, --fast-quit', 'quit app after window close (OSX only), defaults to false') - .option('-u, --user-agent ', 'set the user agent string for the app') - .option('--honest', 'prevent the nativefied app from changing the user agent string to masquerade as a regular chrome browser') - .option('--ignore-certificate', 'ignore certificate related errors') - .option('--insecure', 'enable loading of insecure content, defaults to false') - .option('--flash', 'if flash should be enabled') - .option('--flash-path ', 'path to Chrome flash plugin, find it in `Chrome://plugins`') - .option('--inject ', 'path to a CSS/JS file to be injected', collect, []) - .option('--full-screen', 'if the app should always be started in full screen') - .option('--maximize', 'if the app should always be started maximized') - .option('--hide-window-frame', 'disable window frame and controls') - .option('--verbose', 'if verbose logs should be displayed') - .option('--disable-context-menu', 'disable the context menu') - .option('--disable-dev-tools', 'disable developer tools') - .option('--zoom ', 'default zoom factor to use when the app is opened, defaults to 1.0', parseFloat) - .option('--internal-urls ', 'regular expression of URLs to consider "internal"; all other URLs will be opened in an external browser. (default: URLs on same second-level domain as app)') - .option('--crash-reporter ', 'remote server URL to send crash reports') - .option('--single-instance', 'allow only a single instance of the application') - .parse(process.argv); + if (!process.argv.slice(2).length) { + program.help(); + } - if (!process.argv.slice(2).length) { - program.help(); + nativefier(program, (error, appPath) => { + if (error) { + console.error(error); + return; } - nativefier(program, (error, appPath) => { - if (error) { - console.error(error); - return; - } - - if (!appPath) { - // app exists and --overwrite is not passed - return; - } - console.log(`App built to ${appPath}`); - }); + if (!appPath) { + // app exists and --overwrite is not passed + return; + } + console.log(`App built to ${appPath}`); + }); } diff --git a/src/helpers/convertToIcns.js b/src/helpers/convertToIcns.js index 09bea58..5c86199 100644 --- a/src/helpers/convertToIcns.js +++ b/src/helpers/convertToIcns.js @@ -2,6 +2,7 @@ import shell from 'shelljs'; import path from 'path'; import tmp from 'tmp'; import helpers from './helpers'; + const isOSX = helpers.isOSX; tmp.setGracefulCleanup(); @@ -20,27 +21,27 @@ const PNG_TO_ICNS_BIN_PATH = path.join(__dirname, '../..', 'bin/convertToIcns'); * @param {pngToIcnsCallback} callback */ function convertToIcns(pngSrc, icnsDest, callback) { - if (!isOSX()) { - callback('OSX is required to convert .png to .icns icon', pngSrc); + if (!isOSX()) { + callback('OSX is required to convert .png to .icns icon', pngSrc); + return; + } + + shell.exec(`${PNG_TO_ICNS_BIN_PATH} ${pngSrc} ${icnsDest}`, { silent: true }, (exitCode, stdOut, stdError) => { + if (stdOut.includes('icon.iconset:error') || exitCode) { + if (exitCode) { + callback({ + stdOut, + stdError, + }, pngSrc); return; + } + + callback(stdOut, pngSrc); + return; } - shell.exec(`${PNG_TO_ICNS_BIN_PATH} ${pngSrc} ${icnsDest}`, {silent: true}, (exitCode, stdOut, stdError) => { - if (stdOut.includes('icon.iconset:error') || exitCode) { - if (exitCode) { - callback({ - stdOut: stdOut, - stdError: stdError - }, pngSrc); - return; - } - - callback(stdOut, pngSrc); - return; - } - - callback(null, icnsDest); - }); + callback(null, icnsDest); + }); } /** @@ -49,9 +50,9 @@ function convertToIcns(pngSrc, icnsDest, callback) { * @param {pngToIcnsCallback} callback */ function convertToIcnsTmp(pngSrc, callback) { - const tempIconDirObj = tmp.dirSync({unsafeCleanup: true}); - const tempIconDirPath = tempIconDirObj.name; - convertToIcns(pngSrc, `${tempIconDirPath}/icon.icns`, callback); + const tempIconDirObj = tmp.dirSync({ unsafeCleanup: true }); + const tempIconDirPath = tempIconDirObj.name; + convertToIcns(pngSrc, `${tempIconDirPath}/icon.icns`, callback); } export default convertToIcnsTmp; diff --git a/src/helpers/dishonestProgress.js b/src/helpers/dishonestProgress.js index 02e20cf..beac6fe 100644 --- a/src/helpers/dishonestProgress.js +++ b/src/helpers/dishonestProgress.js @@ -1,68 +1,70 @@ import ProgressBar from 'progress'; class DishonestProgress { - constructor(total) { - this.tickParts = total * 10; + constructor(total) { + this.tickParts = total * 10; - this.bar = new ProgressBar(' :task [:bar] :percent', { - complete: '=', - incomplete: ' ', - total: total * this.tickParts, - width: 50, - clear: true - }); + this.bar = new ProgressBar(' :task [:bar] :percent', { + complete: '=', + incomplete: ' ', + total: total * this.tickParts, + width: 50, + clear: true, + }); - this.tickingPrevious = { - message: '', - remainder: 0, - interval: null - }; + this.tickingPrevious = { + message: '', + remainder: 0, + interval: null, + }; + } + + tick(message) { + const { + remainder: prevRemainder, + message: prevMessage, + interval: prevInterval, + } = this.tickingPrevious; + + if (prevRemainder) { + this.bar.tick(prevRemainder, { + task: prevMessage, + }); + clearInterval(prevInterval); } - tick(message) { - - const {remainder: prevRemainder, message: prevMessage, interval: prevInterval} = this.tickingPrevious; - - if (prevRemainder) { - this.bar.tick(prevRemainder, { - task: prevMessage - }); - clearInterval(prevInterval); - } - - const realRemainder = this.bar.total - this.bar.curr; - if (realRemainder === this.tickParts) { - this.bar.tick(this.tickParts, { - task: message - }); - return; - } - - this.bar.tick({ - task: message - }); - - this.tickingPrevious = { - message: message, - remainder: this.tickParts, - interval: null - }; - - this.tickingPrevious.remainder -= 1; - - this.tickingPrevious.interval = setInterval(() => { - if (this.tickingPrevious.remainder === 1) { - clearInterval(this.tickingPrevious.interval); - return; - } - - this.bar.tick({ - task: message - }); - this.tickingPrevious.remainder -= 1; - }, 200); - + const realRemainder = this.bar.total - this.bar.curr; + if (realRemainder === this.tickParts) { + this.bar.tick(this.tickParts, { + task: message, + }); + return; } + + this.bar.tick({ + task: message, + }); + + this.tickingPrevious = { + message, + remainder: this.tickParts, + interval: null, + }; + + this.tickingPrevious.remainder -= 1; + + this.tickingPrevious.interval = setInterval(() => { + if (this.tickingPrevious.remainder === 1) { + clearInterval(this.tickingPrevious.interval); + return; + } + + this.bar.tick({ + task: message, + }); + this.tickingPrevious.remainder -= 1; + }, 200); + } } export default DishonestProgress; diff --git a/src/helpers/helpers.js b/src/helpers/helpers.js index f0bc904..1341650 100644 --- a/src/helpers/helpers.js +++ b/src/helpers/helpers.js @@ -4,100 +4,100 @@ import hasBinary from 'hasbin'; import path from 'path'; function isOSX() { - return os.platform() === 'darwin'; + return os.platform() === 'darwin'; } function isWindows() { - return os.platform() === 'win32'; + return os.platform() === 'win32'; } function downloadFile(fileUrl) { - return axios.get( - fileUrl, { - responseType: 'arraybuffer' - }) - .then(function(response) { - if (!response.data) { - return null; - } - return { - data: response.data, - ext: path.extname(fileUrl) - }; - }); + return axios.get( + fileUrl, { + responseType: 'arraybuffer', + }) + .then((response) => { + if (!response.data) { + return null; + } + return { + data: response.data, + ext: path.extname(fileUrl), + }; + }); } function allowedIconFormats(platform) { - const hasIdentify = hasBinary.sync('identify'); - const hasConvert = hasBinary.sync('convert'); - const hasIconUtil = hasBinary.sync('iconutil'); + const hasIdentify = hasBinary.sync('identify'); + const hasConvert = hasBinary.sync('convert'); + const hasIconUtil = hasBinary.sync('iconutil'); - const pngToIcns = hasConvert && hasIconUtil; - const pngToIco = hasConvert; - const icoToIcns = pngToIcns && hasIdentify; - const icoToPng = hasConvert; + const pngToIcns = hasConvert && hasIconUtil; + const pngToIco = hasConvert; + const icoToIcns = pngToIcns && hasIdentify; + const icoToPng = hasConvert; - // todo scripts for the following - const icnsToPng = false; - const icnsToIco = false; + // todo scripts for the following + const icnsToPng = false; + const icnsToIco = false; - const formats = []; - - // todo shell scripting is not supported on windows, temporary override - if (isWindows()) { - switch (platform) { - case 'darwin': - formats.push('.icns'); - break; - case 'linux': - formats.push('.png'); - break; - case 'win32': - formats.push('.ico'); - break; - default: - throw `function allowedIconFormats error: Unknown platform ${platform}`; - } - return formats; - } + const formats = []; + // todo shell scripting is not supported on windows, temporary override + if (isWindows()) { switch (platform) { - case 'darwin': - formats.push('.icns'); - if (pngToIcns) { - formats.push('.png'); - } - if (icoToIcns) { - formats.push('.ico'); - } - break; - case 'linux': - formats.push('.png'); - if (icoToPng) { - formats.push('.ico'); - } - if (icnsToPng) { - formats.push('.icns'); - } - break; - case 'win32': - formats.push('.ico'); - if (pngToIco) { - formats.push('.png'); - } - if (icnsToIco) { - formats.push('.icns'); - } - break; - default: - throw `function allowedIconFormats error: Unknown platform ${platform}`; + case 'darwin': + formats.push('.icns'); + break; + case 'linux': + formats.push('.png'); + break; + case 'win32': + formats.push('.ico'); + break; + default: + throw new Error(`function allowedIconFormats error: Unknown platform ${platform}`); } return formats; + } + + switch (platform) { + case 'darwin': + formats.push('.icns'); + if (pngToIcns) { + formats.push('.png'); + } + if (icoToIcns) { + formats.push('.ico'); + } + break; + case 'linux': + formats.push('.png'); + if (icoToPng) { + formats.push('.ico'); + } + if (icnsToPng) { + formats.push('.icns'); + } + break; + case 'win32': + formats.push('.ico'); + if (pngToIco) { + formats.push('.png'); + } + if (icnsToIco) { + formats.push('.icns'); + } + break; + default: + throw new Error(`function allowedIconFormats error: Unknown platform ${platform}`); + } + return formats; } export default { - isOSX, - isWindows, - downloadFile, - allowedIconFormats + isOSX, + isWindows, + downloadFile, + allowedIconFormats, }; diff --git a/src/helpers/iconShellHelpers.js b/src/helpers/iconShellHelpers.js index 16ebc37..947edb7 100644 --- a/src/helpers/iconShellHelpers.js +++ b/src/helpers/iconShellHelpers.js @@ -2,15 +2,16 @@ import shell from 'shelljs'; import path from 'path'; import tmp from 'tmp'; import helpers from './helpers'; -const {isWindows, isOSX} = helpers; + +const { isWindows, isOSX } = helpers; tmp.setGracefulCleanup(); const SCRIPT_PATHS = { - singleIco: path.join(__dirname, '../..', 'bin/singleIco'), - convertToPng: path.join(__dirname, '../..', 'bin/convertToPng'), - convertToIco: path.join(__dirname, '../..', 'bin/convertToIco'), - convertToIcns: path.join(__dirname, '../..', 'bin/convertToIcns') + singleIco: path.join(__dirname, '../..', 'bin/singleIco'), + convertToPng: path.join(__dirname, '../..', 'bin/convertToPng'), + convertToIco: path.join(__dirname, '../..', 'bin/convertToIco'), + convertToIcns: path.join(__dirname, '../..', 'bin/convertToIcns'), }; /** @@ -20,29 +21,29 @@ const SCRIPT_PATHS = { * @param {string} dest has to be a .ico path */ function iconShellHelper(shellScriptPath, icoSrc, dest) { - return new Promise((resolve, reject) => { - if (isWindows()) { - reject('OSX or Linux is required'); - return; - } + return new Promise((resolve, reject) => { + if (isWindows()) { + reject('OSX or Linux is required'); + return; + } - shell.exec(`${shellScriptPath} ${icoSrc} ${dest}`, {silent: true}, (exitCode, stdOut, stdError) => { - if (exitCode) { - reject({ - stdOut: stdOut, - stdError: stdError - }); - return; - } - - resolve(dest); + shell.exec(`${shellScriptPath} ${icoSrc} ${dest}`, { silent: true }, (exitCode, stdOut, stdError) => { + if (exitCode) { + reject({ + stdOut, + stdError, }); + return; + } + + resolve(dest); }); + }); } function getTmpDirPath() { - const tempIconDirObj = tmp.dirSync({unsafeCleanup: true}); - return tempIconDirObj.name; + const tempIconDirObj = tmp.dirSync({ unsafeCleanup: true }); + return tempIconDirObj.name; } /** @@ -52,27 +53,27 @@ function getTmpDirPath() { */ function singleIco(icoSrc) { - return iconShellHelper(SCRIPT_PATHS.singleIco, icoSrc, `${getTmpDirPath()}/icon.ico`); + return iconShellHelper(SCRIPT_PATHS.singleIco, icoSrc, `${getTmpDirPath()}/icon.ico`); } function convertToPng(icoSrc) { - return iconShellHelper(SCRIPT_PATHS.convertToPng, icoSrc, `${getTmpDirPath()}/icon.png`); + return iconShellHelper(SCRIPT_PATHS.convertToPng, icoSrc, `${getTmpDirPath()}/icon.png`); } function convertToIco(icoSrc) { - return iconShellHelper(SCRIPT_PATHS.convertToIco, icoSrc, `${getTmpDirPath()}/icon.ico`); + return iconShellHelper(SCRIPT_PATHS.convertToIco, icoSrc, `${getTmpDirPath()}/icon.ico`); } function convertToIcns(icoSrc) { - if (!isOSX()) { - return new Promise((resolve, reject) => reject('OSX is required to convert to a .icns icon')); - } - return iconShellHelper(SCRIPT_PATHS.convertToIcns, icoSrc, `${getTmpDirPath()}/icon.icns`); + if (!isOSX()) { + return new Promise((resolve, reject) => reject('OSX is required to convert to a .icns icon')); + } + return iconShellHelper(SCRIPT_PATHS.convertToIcns, icoSrc, `${getTmpDirPath()}/icon.icns`); } export default { - singleIco, - convertToPng, - convertToIco, - convertToIcns + singleIco, + convertToPng, + convertToIco, + convertToIcns, }; diff --git a/src/helpers/packagerConsole.js b/src/helpers/packagerConsole.js index 59d85b6..a7ea9b6 100644 --- a/src/helpers/packagerConsole.js +++ b/src/helpers/packagerConsole.js @@ -1,27 +1,29 @@ +// TODO: remove this file and use quiet mode of new version of electron packager class PackagerConsole { - constructor() { - this.logs = []; - } + constructor() { + this.logs = []; + } - _log(...messages) { - this.logs.push(...messages); - } + _log(...messages) { + this.logs.push(...messages); + } - override() { - this.consoleError = console.error; + override() { + this.consoleError = console.error; - // need to bind because somehow when _log() is called this refers to console - console.error = this._log.bind(this); - } + // need to bind because somehow when _log() is called this refers to console + // eslint-disable-next-line no-underscore-dangle + console.error = this._log.bind(this); + } - restore() { - console.error = this.consoleError; - } + restore() { + console.error = this.consoleError; + } - playback() { - console.log(this.logs.join(' ')); - } + playback() { + console.log(this.logs.join(' ')); + } } export default PackagerConsole; diff --git a/src/infer/inferIcon.js b/src/infer/inferIcon.js index dfdf419..a29cf6a 100644 --- a/src/infer/inferIcon.js +++ b/src/infer/inferIcon.js @@ -5,117 +5,100 @@ import tmp from 'tmp'; import gitCloud from 'gitcloud'; import helpers from './../helpers/helpers'; -const {downloadFile, allowedIconFormats} = helpers; +const { downloadFile, allowedIconFormats } = helpers; tmp.setGracefulCleanup(); const GITCLOUD_SPACE_DELIMITER = '-'; -function inferIconFromStore(targetUrl, platform) { - const allowedFormats = allowedIconFormats(platform); - - return gitCloud('http://jiahaog.com/nativefier-icons/') - .then(fileIndex => { - const iconWithScores = mapIconWithMatchScore(fileIndex, targetUrl); - const maxScore = getMaxMatchScore(iconWithScores); - - if (maxScore === 0) { - return null; - } - - const matchingIcons = getMatchingIcons(iconWithScores, maxScore); - - let matchingUrl; - for (let format of allowedFormats) { - for (let icon of matchingIcons) { - if (icon.ext !== format) { - continue; - } - matchingUrl = icon.url; - } - } - - if (!matchingUrl) { - return null; - } - return downloadFile(matchingUrl); - }); -} - -function mapIconWithMatchScore(fileIndex, targetUrl) { - const normalisedTargetUrl = targetUrl.toLowerCase(); - return fileIndex - .map(item => { - const itemWords = item.name.split(GITCLOUD_SPACE_DELIMITER); - const score = itemWords.reduce((currentScore, word) => { - if (normalisedTargetUrl.includes(word)) { - return currentScore + 1; - } - return currentScore; - }, 0); - - return Object.assign({}, - item, - {score} - ); - }); -} - function getMaxMatchScore(iconWithScores) { - return iconWithScores.reduce((maxScore, currentIcon) => { - const currentScore = currentIcon.score; - if (currentScore > maxScore) { - return currentScore; - } - return maxScore; - }, 0); + return iconWithScores.reduce((maxScore, currentIcon) => { + const currentScore = currentIcon.score; + if (currentScore > maxScore) { + return currentScore; + } + return maxScore; + }, 0); } /** * also maps ext to icon object */ -function getMatchingIcons(iconWithScores, maxScore) { - return iconWithScores - .filter(item => { - return item.score === maxScore; - }) - .map(item => { - return Object.assign( - {}, - item, - {ext: path.extname(item.url)} - ); - }); +function getMatchingIcons(iconsWithScores, maxScore) { + return iconsWithScores + .filter(item => item.score === maxScore) + .map(item => Object.assign({}, item, { ext: path.extname(item.url) })); } -function writeFilePromise(outPath, data) { - return new Promise((resolve, reject) => { - fs.writeFile(outPath, data, error => { - if (error) { - reject(error); - return; - } - resolve(outPath); - }); +function mapIconWithMatchScore(fileIndex, targetUrl) { + const normalisedTargetUrl = targetUrl.toLowerCase(); + return fileIndex + .map((item) => { + const itemWords = item.name.split(GITCLOUD_SPACE_DELIMITER); + const score = itemWords.reduce((currentScore, word) => { + if (normalisedTargetUrl.includes(word)) { + return currentScore + 1; + } + return currentScore; + }, 0); + + return Object.assign({}, item, { score }); }); } -function inferFromPage(targetUrl, platform, outDir) { - let preferredExt = '.png'; - if (platform === 'win32') { - preferredExt = '.ico'; - } +function inferIconFromStore(targetUrl, platform) { + const allowedFormats = new Set(allowedIconFormats(platform)); - // todo might want to pass list of preferences instead - return pageIcon(targetUrl, {ext: preferredExt}) - .then(icon => { - if (!icon) { - return null; - } + return gitCloud('http://jiahaog.com/nativefier-icons/') + .then((fileIndex) => { + const iconWithScores = mapIconWithMatchScore(fileIndex, targetUrl); + const maxScore = getMaxMatchScore(iconWithScores); - const outfilePath = path.join(outDir, `/icon${icon.ext}`); - return writeFilePromise(outfilePath, icon.data); - }); + if (maxScore === 0) { + return null; + } + + const iconsMatchingScore = getMatchingIcons(iconWithScores, maxScore); + const iconsMatchingExt = iconsMatchingScore.filter(icon => allowedFormats.has(icon.ext)); + const matchingIcon = iconsMatchingExt[0]; + const iconUrl = matchingIcon && matchingIcon.url; + + if (!iconUrl) { + return null; + } + return downloadFile(iconUrl); + }); } + +function writeFilePromise(outPath, data) { + return new Promise((resolve, reject) => { + fs.writeFile(outPath, data, (error) => { + if (error) { + reject(error); + return; + } + resolve(outPath); + }); + }); +} + +function inferFromPage(targetUrl, platform, outDir) { + let preferredExt = '.png'; + if (platform === 'win32') { + preferredExt = '.ico'; + } + + // todo might want to pass list of preferences instead + return pageIcon(targetUrl, { ext: preferredExt }) + .then((icon) => { + if (!icon) { + return null; + } + + const outfilePath = path.join(outDir, `/icon${icon.ext}`); + return writeFilePromise(outfilePath, icon.data); + }); +} + /** * * @param {string} targetUrl @@ -123,16 +106,15 @@ function inferFromPage(targetUrl, platform, outDir) { * @param {string} outDir */ function inferIconFromUrlToPath(targetUrl, platform, outDir) { + return inferIconFromStore(targetUrl, platform) + .then((icon) => { + if (!icon) { + return inferFromPage(targetUrl, platform, outDir); + } - return inferIconFromStore(targetUrl, platform) - .then(icon => { - if (!icon) { - return inferFromPage(targetUrl, platform, outDir); - } - - const outfilePath = path.join(outDir, `/icon${icon.ext}`); - return writeFilePromise(outfilePath, icon.data); - }); + const outfilePath = path.join(outDir, `/icon${icon.ext}`); + return writeFilePromise(outfilePath, icon.data); + }); } /** @@ -140,9 +122,9 @@ function inferIconFromUrlToPath(targetUrl, platform, outDir) { * @param {string} platform */ function inferIcon(targetUrl, platform) { - const tmpObj = tmp.dirSync({unsafeCleanup: true}); - const tmpPath = tmpObj.name; - return inferIconFromUrlToPath(targetUrl, platform, tmpPath); + const tmpObj = tmp.dirSync({ unsafeCleanup: true }); + const tmpPath = tmpObj.name; + return inferIconFromUrlToPath(targetUrl, platform, tmpPath); } export default inferIcon; diff --git a/src/infer/inferOs.js b/src/infer/inferOs.js index 2f091c9..f0c1a31 100644 --- a/src/infer/inferOs.js +++ b/src/infer/inferOs.js @@ -1,23 +1,23 @@ import os from 'os'; function inferPlatform() { - const platform = os.platform(); - if (platform === 'darwin' || platform === 'win32' || platform === 'linux') { - return platform; - } + const platform = os.platform(); + if (platform === 'darwin' || platform === 'win32' || platform === 'linux') { + return platform; + } - throw `Untested platform ${platform} detected`; + throw new Error(`Untested platform ${platform} detected`); } function inferArch() { - const arch = os.arch(); - if (arch !== 'ia32' && arch !== 'x64') { - throw `Incompatible architecture ${arch} detected`; - } - return arch; + const arch = os.arch(); + if (arch !== 'ia32' && arch !== 'x64') { + throw new Error(`Incompatible architecture ${arch} detected`); + } + return arch; } export default { - inferPlatform: inferPlatform, - inferArch: inferArch + inferPlatform, + inferArch, }; diff --git a/src/infer/inferTitle.js b/src/infer/inferTitle.js index a23056a..d20a397 100644 --- a/src/infer/inferTitle.js +++ b/src/infer/inferTitle.js @@ -4,19 +4,19 @@ import cheerio from 'cheerio'; const USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36'; function inferTitle(url) { - const options = { - method: 'get', - url, - headers: { - // fake a user agent because pages like http://messenger.com will throw 404 error - 'User-Agent': USER_AGENT - } - }; + const options = { + method: 'get', + url, + headers: { + // fake a user agent because pages like http://messenger.com will throw 404 error + 'User-Agent': USER_AGENT, + }, + }; - return axios(options).then(({data}) => { - const $ = cheerio.load(data); - return $('title').first().text().replace(/\//g, ''); - }); + return axios(options).then(({ data }) => { + const $ = cheerio.load(data); + return $('title').first().text().replace(/\//g, ''); + }); } export default inferTitle; diff --git a/src/infer/inferUserAgent.js b/src/infer/inferUserAgent.js index e10bd0e..7f9258e 100644 --- a/src/infer/inferUserAgent.js +++ b/src/infer/inferUserAgent.js @@ -6,50 +6,49 @@ const ELECTRON_VERSIONS_URL = 'https://atom.io/download/atom-shell/index.json'; const DEFAULT_CHROME_VERSION = '56.0.2924.87'; function getChromeVersionForElectronVersion(electronVersion, url = ELECTRON_VERSIONS_URL) { - return axios.get(url, {timeout: 5000}) - .then(response => { - if (response.status !== 200) { - throw `Bad request: Status code ${response.status}`; - } + return axios.get(url, { timeout: 5000 }) + .then((response) => { + if (response.status !== 200) { + throw new Error(`Bad request: Status code ${response.status}`); + } - const data = response.data; - const electronVersionToChromeVersion = _.zipObject(data.map(d => d.version), data.map(d => d.chrome)); + const data = response.data; + const electronVersionToChromeVersion = _.zipObject(data.map(d => d.version), + data.map(d => d.chrome)); - if (!(electronVersion in electronVersionToChromeVersion)) { - throw `Electron version '${electronVersion}' not found in retrieved version list!`; - } + if (!(electronVersion in electronVersionToChromeVersion)) { + throw new Error(`Electron version '${electronVersion}' not found in retrieved version list!`); + } - return electronVersionToChromeVersion[electronVersion]; - }); + return electronVersionToChromeVersion[electronVersion]; + }); } export function getUserAgentString(chromeVersion, platform) { - let userAgent; - switch (platform) { - case 'darwin': - userAgent = `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`; - break; - case 'win32': - userAgent = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`; - break; - case 'linux': - userAgent = `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`; - break; - default: - throw 'Error invalid platform specified to getUserAgentString()'; - } - return userAgent; + let userAgent; + switch (platform) { + case 'darwin': + userAgent = `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`; + break; + case 'win32': + userAgent = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`; + break; + case 'linux': + userAgent = `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`; + break; + default: + throw new Error('Error invalid platform specified to getUserAgentString()'); + } + return userAgent; } function inferUserAgent(electronVersion, platform, url = ELECTRON_VERSIONS_URL) { - return getChromeVersionForElectronVersion(electronVersion, url) - .then(chromeVersion => { - return getUserAgentString(chromeVersion, platform); - }) - .catch(() => { - log.warn(`Unable to infer chrome version for user agent, using ${DEFAULT_CHROME_VERSION}`); - return getUserAgentString(DEFAULT_CHROME_VERSION, platform); - }); + return getChromeVersionForElectronVersion(electronVersion, url) + .then(chromeVersion => getUserAgentString(chromeVersion, platform)) + .catch(() => { + log.warn(`Unable to infer chrome version for user agent, using ${DEFAULT_CHROME_VERSION}`); + return getUserAgentString(DEFAULT_CHROME_VERSION, platform); + }); } export default inferUserAgent; diff --git a/src/options/normalizeUrl.js b/src/options/normalizeUrl.js index 0fe1263..d4d487c 100644 --- a/src/options/normalizeUrl.js +++ b/src/options/normalizeUrl.js @@ -2,25 +2,25 @@ import url from 'url'; import validator from 'validator'; function appendProtocol(testUrl) { - const parsed = url.parse(testUrl); - if (!parsed.protocol) { - return `http://${testUrl}`; - } - return testUrl; + const parsed = url.parse(testUrl); + if (!parsed.protocol) { + return `http://${testUrl}`; + } + return testUrl; } function normalizeUrl(testUrl) { - const urlWithProtocol = appendProtocol(testUrl); + const urlWithProtocol = appendProtocol(testUrl); - const validatorOptions = { - require_protocol: true, - require_tld: false, - allow_trailing_dot: true // mDNS addresses, https://github.com/jiahaog/nativefier/issues/308 - }; - if (!validator.isURL(urlWithProtocol, validatorOptions)) { - throw `Your Url: "${urlWithProtocol}" is invalid!`; - } - return urlWithProtocol; + const validatorOptions = { + require_protocol: true, + require_tld: false, + allow_trailing_dot: true, // mDNS addresses, https://github.com/jiahaog/nativefier/issues/308 + }; + if (!validator.isURL(urlWithProtocol, validatorOptions)) { + throw new Error(`Your Url: "${urlWithProtocol}" is invalid!`); + } + return urlWithProtocol; } export default normalizeUrl; diff --git a/src/options/optionsMain.js b/src/options/optionsMain.js index 4d16793..eabb7f5 100644 --- a/src/options/optionsMain.js +++ b/src/options/optionsMain.js @@ -11,13 +11,31 @@ import inferUserAgent from './../infer/inferUserAgent'; import normalizeUrl from './normalizeUrl'; import packageJson from './../../package.json'; -const {inferPlatform, inferArch} = inferOs; +const { inferPlatform, inferArch } = inferOs; const PLACEHOLDER_APP_DIR = path.join(__dirname, '../../', 'app'); const ELECTRON_VERSION = '1.6.6'; - const DEFAULT_APP_NAME = 'APP'; +function sanitizeFilename(platform, str) { + let result = sanitizeFilenameLib(str); + + // remove all non ascii or use default app name + // eslint-disable-next-line no-control-regex + result = result.replace(/[^\x00-\x7F]/g, '') || DEFAULT_APP_NAME; + + // spaces will cause problems with Ubuntu when pinned to the dock + if (platform === 'linux') { + return _.kebabCase(result); + } + return result; +} + +function sanitizeOptions(options) { + const name = sanitizeFilename(options.platform, options.name); + return Object.assign({}, options, { name }); +} + /** * @callback optionsCallback * @param error @@ -30,143 +48,128 @@ const DEFAULT_APP_NAME = 'APP'; * @param {optionsCallback} callback */ function optionsFactory(inpOptions, callback) { - - const options = { - dir: PLACEHOLDER_APP_DIR, - name: inpOptions.name, - targetUrl: normalizeUrl(inpOptions.targetUrl), - platform: inpOptions.platform || inferPlatform(), - arch: inpOptions.arch || inferArch(), - electronVersion: inpOptions.electronVersion || ELECTRON_VERSION, - nativefierVersion: packageJson.version, - out: inpOptions.out || process.cwd(), - overwrite: inpOptions.overwrite, - asar: inpOptions.conceal || false, - icon: inpOptions.icon, - counter: inpOptions.counter || false, - width: inpOptions.width || 1280, - height: inpOptions.height || 800, - minWidth: inpOptions.minWidth, - minHeight: inpOptions.minHeight, - maxWidth: inpOptions.maxWidth, - maxHeight: inpOptions.maxHeight, - showMenuBar: inpOptions.showMenuBar || false, - fastQuit: inpOptions.fastQuit || false, - userAgent: inpOptions.userAgent, - ignoreCertificate: inpOptions.ignoreCertificate || false, - insecure: inpOptions.insecure || false, - flashPluginDir: inpOptions.flashPath || inpOptions.flash || null, - inject: inpOptions.inject || null, - ignore: 'src', - fullScreen: inpOptions.fullScreen || false, - maximize: inpOptions.maximize || false, - hideWindowFrame: inpOptions.hideWindowFrame, - verbose: inpOptions.verbose, - disableContextMenu: inpOptions.disableContextMenu, - disableDevTools: inpOptions.disableDevTools, - crashReporter: inpOptions.crashReporter, + const options = { + dir: PLACEHOLDER_APP_DIR, + name: inpOptions.name, + targetUrl: normalizeUrl(inpOptions.targetUrl), + platform: inpOptions.platform || inferPlatform(), + arch: inpOptions.arch || inferArch(), + electronVersion: inpOptions.electronVersion || ELECTRON_VERSION, + nativefierVersion: packageJson.version, + out: inpOptions.out || process.cwd(), + overwrite: inpOptions.overwrite, + asar: inpOptions.conceal || false, + icon: inpOptions.icon, + counter: inpOptions.counter || false, + width: inpOptions.width || 1280, + height: inpOptions.height || 800, + minWidth: inpOptions.minWidth, + minHeight: inpOptions.minHeight, + maxWidth: inpOptions.maxWidth, + maxHeight: inpOptions.maxHeight, + showMenuBar: inpOptions.showMenuBar || false, + fastQuit: inpOptions.fastQuit || false, + userAgent: inpOptions.userAgent, + ignoreCertificate: inpOptions.ignoreCertificate || false, + insecure: inpOptions.insecure || false, + flashPluginDir: inpOptions.flashPath || inpOptions.flash || null, + inject: inpOptions.inject || null, + ignore: 'src', + fullScreen: inpOptions.fullScreen || false, + maximize: inpOptions.maximize || false, + hideWindowFrame: inpOptions.hideWindowFrame, + verbose: inpOptions.verbose, + disableContextMenu: inpOptions.disableContextMenu, + disableDevTools: inpOptions.disableDevTools, + crashReporter: inpOptions.crashReporter, // workaround for electron-packager#375 - tmpdir: false, - zoom: inpOptions.zoom || 1.0, - internalUrls: inpOptions.internalUrls || null, - singleInstance: inpOptions.singleInstance || false - }; + tmpdir: false, + zoom: inpOptions.zoom || 1.0, + internalUrls: inpOptions.internalUrls || null, + singleInstance: inpOptions.singleInstance || false, + }; - if (options.verbose) { - log.setLevel('trace'); - } else { - log.setLevel('error'); + if (options.verbose) { + log.setLevel('trace'); + } else { + log.setLevel('error'); + } + + if (options.flashPluginDir) { + options.insecure = true; + } + + if (inpOptions.honest) { + options.userAgent = null; + } + + if (options.platform.toLowerCase() === 'windows') { + options.platform = 'win32'; + } + + if (options.platform.toLowerCase() === 'osx' || options.platform.toLowerCase() === 'mac') { + options.platform = 'darwin'; + } + + if (options.width > options.maxWidth) { + options.width = options.maxWidth; + } + + if (options.height > options.maxHeight) { + options.height = options.maxHeight; + } + + async.waterfall([ + (callback) => { + if (options.userAgent) { + callback(); + return; + } + inferUserAgent(options.electronVersion, options.platform) + .then((userAgent) => { + options.userAgent = userAgent; + callback(); + }) + .catch(callback); + }, + (callback) => { + if (options.icon) { + callback(); + return; + } + inferIcon(options.targetUrl, options.platform) + .then((pngPath) => { + options.icon = pngPath; + callback(); + }) + .catch((error) => { + log.warn('Cannot automatically retrieve the app icon:', error); + callback(); + }); + }, + (callback) => { + // length also checks if its the commanderJS function or a string + if (options.name && options.name.length > 0) { + callback(); + return; + } + options.name = DEFAULT_APP_NAME; + + inferTitle(options.targetUrl).then((pageTitle) => { + options.name = pageTitle; + }).catch((error) => { + log.warn(`Unable to automatically determine app name, falling back to '${DEFAULT_APP_NAME}'. Reason: ${error}`); + }).then(() => { + callback(); + }); + }, + ], (error) => { + if (error) { + callback(error); + return; } - - if (options.flashPluginDir) { - options.insecure = true; - } - - if (inpOptions.honest) { - options.userAgent = null; - } - - if (options.platform.toLowerCase() === 'windows') { - options.platform = 'win32'; - } - - if (options.platform.toLowerCase() === 'osx' || options.platform.toLowerCase() === 'mac') { - options.platform = 'darwin'; - } - - if (options.width > options.maxWidth) { - options.width = options.maxWidth; - } - - if (options.height > options.maxHeight) { - options.height = options.maxHeight; - } - - async.waterfall([ - callback => { - if (options.userAgent) { - callback(); - return; - } - inferUserAgent(options.electronVersion, options.platform) - .then(userAgent => { - options.userAgent = userAgent; - callback(); - }) - .catch(callback); - }, - callback => { - if (options.icon) { - callback(); - return; - } - inferIcon(options.targetUrl, options.platform) - .then(pngPath => { - options.icon = pngPath; - callback(); - }) - .catch(error => { - log.warn('Cannot automatically retrieve the app icon:', error); - callback(); - }); - }, - callback => { - // length also checks if its the commanderJS function or a string - if (options.name && options.name.length > 0) { - callback(); - return; - } - options.name = DEFAULT_APP_NAME; - - inferTitle(options.targetUrl).then(pageTitle => { - options.name = pageTitle; - }).catch(error => { - log.warn(`Unable to automatically determine app name, falling back to '${DEFAULT_APP_NAME}'. Reason: ${error}`); - }).then(() => { - callback(); - }); - } - ], error => { - callback(error, sanitizeOptions(options)); - }); -} - -function sanitizeFilename(platform, str) { - let result = sanitizeFilenameLib(str); - - // remove all non ascii or use default app name - result = result.replace(/[^\x00-\x7F]/g, '') || DEFAULT_APP_NAME; - - // spaces will cause problems with Ubuntu when pinned to the dock - if (platform === 'linux') { - return _.kebabCase(result); - } - return result; -} - -function sanitizeOptions(options) { - options.name = sanitizeFilename(options.platform, options.name); - return options; + callback(null, sanitizeOptions(options)); + }); } export default optionsFactory; diff --git a/test/module/.eslintrc.yml b/test/module/.eslintrc.yml new file mode 100644 index 0000000..9808c3b --- /dev/null +++ b/test/module/.eslintrc.yml @@ -0,0 +1,2 @@ +env: + mocha: true diff --git a/test/module/getIconSpec.js b/test/module/getIconSpec.js index e773d07..af487aa 100644 --- a/test/module/getIconSpec.js +++ b/test/module/getIconSpec.js @@ -7,36 +7,36 @@ import os from 'os'; import path from 'path'; import convertToIcns from './../../lib/helpers/convertToIcns'; -let assert = chai.assert; +const assert = chai.assert; // Prerequisite for test: to use OSX with sips, iconutil and imagemagick convert function testConvertPng(pngName, done) { - convertToIcns(path.join(__dirname, '../../', 'test-resources', pngName), (error, icnsPath) => { - if (error) { - done(error); - return; - } + convertToIcns(path.join(__dirname, '../../', 'test-resources', pngName), (error, icnsPath) => { + if (error) { + done(error); + return; + } - let stat = fs.statSync(icnsPath); - assert.isTrue(stat.isFile(), 'Output icns file should be a path'); - done(); - }); + const stat = fs.statSync(icnsPath); + assert.isTrue(stat.isFile(), 'Output icns file should be a path'); + done(); + }); } -describe('Get Icon Module', function() { - it('Can convert icons', function() { - if (os.platform() !== 'darwin') { - console.warn('Skipping png conversion tests, OSX is required'); - return; - } +describe('Get Icon Module', () => { + it('Can convert icons', () => { + if (os.platform() !== 'darwin') { + console.warn('Skipping png conversion tests, OSX is required'); + return; + } - it('Can convert a rgb png to icns', function(done) { - testConvertPng('iconSample.png', done); - }); - - it('Can convert a grey png to icns', function(done) { - testConvertPng('iconSampleGrey.png', done); - }); + it('Can convert a rgb png to icns', (done) => { + testConvertPng('iconSample.png', done); }); + + it('Can convert a grey png to icns', (done) => { + testConvertPng('iconSampleGrey.png', done); + }); + }); }); diff --git a/test/module/indexSpec.js b/test/module/indexSpec.js index ab73df0..29e82e6 100644 --- a/test/module/indexSpec.js +++ b/test/module/indexSpec.js @@ -11,65 +11,65 @@ tmp.setGracefulCleanup(); const assert = chai.assert; function checkApp(appPath, inputOptions, callback) { - try { - let relPathToConfig; + try { + let relPathToConfig; - switch (inputOptions.platform) { - case 'darwin': - relPathToConfig = path.join('google-test-app.app', 'Contents/Resources/app'); - break; - case 'linux': - relPathToConfig = 'resources/app'; - break; - case 'win32': - relPathToConfig = 'resources/app'; - break; - default: - throw 'Unknown app platform'; - } - - const nativefierConfigPath = path.join(appPath, relPathToConfig, 'nativefier.json'); - const nativefierConfig = JSON.parse(fs.readFileSync(nativefierConfigPath)); - - assert.strictEqual(inputOptions.targetUrl, nativefierConfig.targetUrl, 'Packaged app must have the same targetUrl as the input parameters'); - // app name is not consistent for linux - // assert.strictEqual(inputOptions.appName, nativefierConfig.name, 'Packaged app must have the same name as the input parameters'); - callback(); - } catch (exception) { - callback(exception); + switch (inputOptions.platform) { + case 'darwin': + relPathToConfig = path.join('google-test-app.app', 'Contents/Resources/app'); + break; + case 'linux': + relPathToConfig = 'resources/app'; + break; + case 'win32': + relPathToConfig = 'resources/app'; + break; + default: + throw new Error('Unknown app platform'); } + + const nativefierConfigPath = path.join(appPath, relPathToConfig, 'nativefier.json'); + const nativefierConfig = JSON.parse(fs.readFileSync(nativefierConfigPath)); + + assert.strictEqual(inputOptions.targetUrl, nativefierConfig.targetUrl, 'Packaged app must have the same targetUrl as the input parameters'); + // app name is not consistent for linux + // assert.strictEqual(inputOptions.appName, nativefierConfig.name, + // 'Packaged app must have the same name as the input parameters'); + callback(); + } catch (exception) { + callback(exception); + } } -describe('Nativefier Module', function() { - this.timeout(240000); - it('Can build an app from a target url', function(done) { - async.eachSeries(PLATFORMS, (platform, callback) => { +describe('Nativefier Module', function () { + this.timeout(240000); + it('Can build an app from a target url', (done) => { + async.eachSeries(PLATFORMS, (platform, callback) => { + const tmpObj = tmp.dirSync({ unsafeCleanup: true }); - const tmpObj = tmp.dirSync({unsafeCleanup: true}); + const tmpPath = tmpObj.name; + const options = { + name: 'google-test-app', + targetUrl: 'http://google.com', + out: tmpPath, + overwrite: true, + platform: null, + }; - const tmpPath = tmpObj.name; - const options = { - name: 'google-test-app', - targetUrl: 'http://google.com', - out: tmpPath, - overwrite: true, - platform: null - }; + options.platform = platform; + nativefier(options, (error, appPath) => { + if (error) { + callback(error); + return; + } - options.platform = platform; - nativefier(options, (error, appPath) => { - if (error) { - callback(error); - return; - } - - checkApp(appPath, options, error => { - callback(error); - }); - }); - }, error => { - done(error); + checkApp(appPath, options, (error) => { + callback(error); }); + }); + }, (error) => { + done(error); }); + }); }); diff --git a/test/module/inferUserAgentSpec.js b/test/module/inferUserAgentSpec.js index 71e16ec..db8acc3 100644 --- a/test/module/inferUserAgentSpec.js +++ b/test/module/inferUserAgentSpec.js @@ -1,49 +1,47 @@ -import inferUserAgent from './../../lib/infer/inferUserAgent'; import chai from 'chai'; import _ from 'lodash'; +import inferUserAgent from './../../lib/infer/inferUserAgent'; const assert = chai.assert; const TEST_RESULT = { - darwin: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36', - win32: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36', - linux: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36' + darwin: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36', + win32: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36', + linux: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36', }; function testPlatform(platform) { - return inferUserAgent('0.37.1', platform) - .then(userAgent => { - assert.equal(userAgent, TEST_RESULT[platform], 'Correct user agent should be inferred'); + return inferUserAgent('0.37.1', platform) + .then((userAgent) => { + assert.equal(userAgent, TEST_RESULT[platform], 'Correct user agent should be inferred'); }); } -describe('Infer User Agent', function() { - this.timeout(15000); - it('Can infer userAgent for all platforms', function(done) { - const testPromises = _.keys(TEST_RESULT).map(platform => { - return testPlatform(platform); - }); - Promise +describe('Infer User Agent', function () { + this.timeout(15000); + it('Can infer userAgent for all platforms', (done) => { + const testPromises = _.keys(TEST_RESULT).map(platform => testPlatform(platform)); + Promise .all(testPromises) .then(() => { - done(); + done(); }) - .catch(error => { - done(error); + .catch((error) => { + done(error); }); - }); + }); - it('Connection error will still get a user agent', function(done) { - const TIMEOUT_URL = 'http://www.google.com:81/'; - inferUserAgent('1.6.7', 'darwin', TIMEOUT_URL) - .then(userAgent => { - assert.equal( + it('Connection error will still get a user agent', (done) => { + const TIMEOUT_URL = 'http://www.google.com:81/'; + inferUserAgent('1.6.7', 'darwin', TIMEOUT_URL) + .then((userAgent) => { + assert.equal( userAgent, 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36', - 'Expect default user agent on connection error' + 'Expect default user agent on connection error', ); - done(); + done(); }) .catch(done); - }); + }); }); diff --git a/test/module/options/normalizeUrlSpec.js b/test/module/options/normalizeUrlSpec.js index 6734529..3b64b2c 100644 --- a/test/module/options/normalizeUrlSpec.js +++ b/test/module/options/normalizeUrlSpec.js @@ -1,25 +1,25 @@ -import normalizeUrl from '../../../src/options/normalizeUrl'; import chai from 'chai'; +import normalizeUrl from '../../../src/options/normalizeUrl'; + const assert = chai.assert; const expect = chai.expect; describe('Normalize URL', () => { - - describe('given a valid URL without a protocol', () => { - it('should allow the url', () => { - assert.equal(normalizeUrl('http://www.google.com'), 'http://www.google.com'); - }); + describe('given a valid URL without a protocol', () => { + it('should allow the url', () => { + assert.equal(normalizeUrl('http://www.google.com'), 'http://www.google.com'); }); + }); - describe('given a valid URL without a protocol', () => { - it('should allow the url and prepend the HTTP protocol', () => { - assert.equal(normalizeUrl('www.google.com'), 'http://www.google.com'); - }); + describe('given a valid URL without a protocol', () => { + it('should allow the url and prepend the HTTP protocol', () => { + assert.equal(normalizeUrl('www.google.com'), 'http://www.google.com'); }); + }); - describe('given an invalid URL', () => { - it('should throw an exception', () => { - expect(() => normalizeUrl('http://ssddfoo bar')).to.throw('Your Url: "http://ssddfoo bar" is invalid!'); - }); + describe('given an invalid URL', () => { + it('should throw an exception', () => { + expect(() => normalizeUrl('http://ssddfoo bar')).to.throw('Your Url: "http://ssddfoo bar" is invalid!'); }); + }); }); diff --git a/webpack.config.js b/webpack.config.js index c1aac0a..14f7c1a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,24 +1,24 @@ -var electronPublicApi = ['electron']; +const electronPublicApi = ['electron']; -var nodeModules = {}; -electronPublicApi.forEach(apiString => { - nodeModules[apiString] = 'commonjs ' + apiString; +const nodeModules = {}; +electronPublicApi.forEach((apiString) => { + nodeModules[apiString] = `commonjs ${apiString}`; }); module.exports = { - target: 'node', - output: { - filename: 'main.js' - }, - node: { - global: false, - __dirname: false - }, - externals: nodeModules, - module: { - loaders: [ - {test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader'} - ] - }, - devtool: 'source-map' + target: 'node', + output: { + filename: 'main.js', + }, + node: { + global: false, + __dirname: false, + }, + externals: nodeModules, + module: { + loaders: [ + { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' }, + ], + }, + devtool: 'source-map', };