Update eslint and use Airbnb style

- Add `npm run lint:fix` command
- Cleanup inferIcon.js logic slightly
This commit is contained in:
Jia Hao Goh 2017-04-29 22:52:12 +08:00
parent 461c7a38f0
commit 8f78dd03af
48 changed files with 1795 additions and 1850 deletions

View File

@ -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'
};

8
.eslintrc.yml Normal file
View File

@ -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']}]

View File

@ -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. 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 ```bash
# Run tests and linting # Run specs and lint
$ npm run ci npm run ci
```
Or you can run them separately: # Run specs only
npm run test
```bash # Run linter only
# Tests npm run lint
$ npm run test
# Lint source files
$ npm run lint
``` ```
Thank you so much for your contribution! Thank you so much for your contribution!

2
app/.eslintrc.yml Normal file
View File

@ -0,0 +1,2 @@
settings:
import/core-modules: [ electron ]

View File

@ -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) { function initContextMenu(mainWindow) {
ipcMain.on('contextMenuOpened', (event, targetHref) => { ipcMain.on('contextMenuOpened', (event, targetHref) => {
const contextMenuTemplate = [ const contextMenuTemplate = [
{ {
label: 'Open with default browser', label: 'Open with default browser',
click: () => { click: () => {
if (targetHref) { if (targetHref) {
shell.openExternal(targetHref); shell.openExternal(targetHref);
return; }
} },
} },
}, {
{ label: 'Open in new window',
label: 'Open in new window', click: () => {
click: () => { if (targetHref) {
if (targetHref) { new BrowserWindow().loadURL(targetHref);
new BrowserWindow().loadURL(targetHref); return;
return; }
}
mainWindow.useDefaultWindowBehaviour = true; mainWindow.useDefaultWindowBehaviour = true;
mainWindow.webContents.send('contextMenuClosed'); mainWindow.webContents.send('contextMenuClosed');
} },
}, },
{ {
label: 'Copy link location', label: 'Copy link location',
click: () => { click: () => {
if (targetHref) { if (targetHref) {
clipboard.writeText(targetHref); clipboard.writeText(targetHref);
return; return;
} }
mainWindow.useDefaultWindowBehaviour = true; mainWindow.useDefaultWindowBehaviour = true;
mainWindow.webContents.send('contextMenuClosed'); mainWindow.webContents.send('contextMenuClosed');
} },
} },
]; ];
const contextMenu = Menu.buildFromTemplate(contextMenuTemplate); const contextMenu = Menu.buildFromTemplate(contextMenuTemplate);
contextMenu.popup(mainWindow); contextMenu.popup(mainWindow);
mainWindow.contextMenuOpen = true; mainWindow.contextMenuOpen = true;
}); });
} }
export default initContextMenu; export default initContextMenu;

View File

@ -1,20 +1,20 @@
import {BrowserWindow, ipcMain} from 'electron'; import { BrowserWindow, ipcMain } from 'electron';
import path from 'path'; import path from 'path';
function createLoginWindow(loginCallback) { function createLoginWindow(loginCallback) {
var loginWindow = new BrowserWindow({ const loginWindow = new BrowserWindow({
width: 300, width: 300,
height: 400, height: 400,
frame: false, frame: false,
resizable: false resizable: false,
}); });
loginWindow.loadURL('file://' + path.join(__dirname, '/static/login/login.html')); loginWindow.loadURL(`file://${path.join(__dirname, '/static/login/login.html')}`);
ipcMain.once('login-message', function(event, usernameAndPassword) { ipcMain.once('login-message', (event, usernameAndPassword) => {
loginCallback(usernameAndPassword[0], usernameAndPassword[1]); loginCallback(usernameAndPassword[0], usernameAndPassword[1]);
loginWindow.close(); loginWindow.close();
}); });
return loginWindow; return loginWindow;
} }
export default createLoginWindow; export default createLoginWindow;

View File

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

View File

@ -1,4 +1,4 @@
import {Menu, shell, clipboard} from 'electron'; import { Menu, shell, clipboard } from 'electron';
/** /**
* @param nativefierVersion * @param nativefierVersion
@ -13,255 +13,265 @@ import {Menu, shell, clipboard} from 'electron';
* @param clearAppData * @param clearAppData
* @param disableDevTools * @param disableDevTools
*/ */
function createMenu({nativefierVersion, appQuit, zoomIn, zoomOut, zoomReset, zoomBuildTimeValue, goBack, goForward, getCurrentUrl, clearAppData, disableDevTools}) { function createMenu({ nativefierVersion,
if (Menu.getApplicationMenu()) { appQuit,
return; zoomIn,
} zoomOut,
const zoomResetLabel = (zoomBuildTimeValue === 1.0) ? zoomReset,
zoomBuildTimeValue,
goBack,
goForward,
getCurrentUrl,
clearAppData,
disableDevTools }) {
if (Menu.getApplicationMenu()) {
return;
}
const zoomResetLabel = (zoomBuildTimeValue === 1.0) ?
'Reset Zoom' : 'Reset Zoom' :
`Reset Zoom (to ${zoomBuildTimeValue * 100}%, set at build time)`; `Reset Zoom (to ${zoomBuildTimeValue * 100}%, set at build time)`;
const template = [ const template = [
{
label: 'Edit',
submenu: [
{ {
label: 'Edit', label: 'Undo',
submenu: [ accelerator: 'CmdOrCtrl+Z',
{ role: 'undo',
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: 'View', label: 'Redo',
submenu: [ accelerator: 'Shift+CmdOrCtrl+Z',
{ role: 'redo',
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', type: 'separator',
role: 'window',
submenu: [
{
label: 'Minimize',
accelerator: 'CmdOrCtrl+M',
role: 'minimize'
},
{
label: 'Close',
accelerator: 'CmdOrCtrl+W',
role: 'close'
}
]
}, },
{ {
label: 'Help', label: 'Cut',
role: 'help', accelerator: 'CmdOrCtrl+X',
submenu: [ role: 'cut',
{ },
label: `Built with Nativefier v${nativefierVersion}`, {
click: () => { label: 'Copy',
shell.openExternal('https://github.com/jiahaog/nativefier'); accelerator: 'CmdOrCtrl+C',
} role: 'copy',
}, },
{ {
label: 'Report an Issue', label: 'Copy Current URL',
click: () => { accelerator: 'CmdOrCtrl+L',
shell.openExternal('https://github.com/jiahaog/nativefier/issues'); click: () => {
} const currentURL = getCurrentUrl();
} clipboard.writeText(currentURL);
] },
} },
]; {
label: 'Paste',
if (disableDevTools) { accelerator: 'CmdOrCtrl+V',
// remove last item (dev tools) from menu > view role: 'paste',
const submenu = template[1].submenu; },
submenu.splice(submenu.length - 1, 1); {
} label: 'Select All',
accelerator: 'CmdOrCtrl+A',
if (process.platform === 'darwin') { role: 'selectall',
template.unshift({ },
label: 'Electron', {
submenu: [ label: 'Clear App Data',
{ click: () => {
label: 'Services', clearAppData();
role: 'services', },
submenu: [] },
}, ],
{ },
type: 'separator' {
}, label: 'View',
{ submenu: [
label: 'Hide App', {
accelerator: 'Command+H', label: 'Back',
role: 'hide' accelerator: 'CmdOrCtrl+[',
}, click: () => {
{ goBack();
label: 'Hide Others', },
accelerator: 'Command+Shift+H', },
role: 'hideothers' {
}, label: 'Forward',
{ accelerator: 'CmdOrCtrl+]',
label: 'Show All', click: () => {
role: 'unhide' goForward();
}, },
{ },
type: 'separator' {
}, label: 'Reload',
{ accelerator: 'CmdOrCtrl+R',
label: 'Quit', click: (item, focusedWindow) => {
accelerator: 'Command+Q', if (focusedWindow) {
click: () => { focusedWindow.reload();
appQuit();
}
}
]
});
template[3].submenu.push(
{
type: 'separator'
},
{
label: 'Bring All to Front',
role: 'front'
} }
); },
} },
{
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); if (disableDevTools) {
Menu.setApplicationMenu(menu); // 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; export default createMenu;

View File

@ -6,39 +6,39 @@ import path from 'path';
const INJECT_CSS_PATH = path.join(__dirname, '..', 'inject/inject.css'); const INJECT_CSS_PATH = path.join(__dirname, '..', 'inject/inject.css');
function isOSX() { function isOSX() {
return os.platform() === 'darwin'; return os.platform() === 'darwin';
} }
function isLinux() { function isLinux() {
return os.platform() === 'linux'; return os.platform() === 'linux';
} }
function isWindows() { function isWindows() {
return os.platform() === 'win32'; return os.platform() === 'win32';
} }
function linkIsInternal(currentUrl, newUrl, internalUrlRegex) { function linkIsInternal(currentUrl, newUrl, internalUrlRegex) {
if (internalUrlRegex) { if (internalUrlRegex) {
var regex = RegExp(internalUrlRegex); const regex = RegExp(internalUrlRegex);
return regex.test(newUrl); return regex.test(newUrl);
} }
var currentDomain = wurl('domain', currentUrl); const currentDomain = wurl('domain', currentUrl);
var newDomain = wurl('domain', newUrl); const newDomain = wurl('domain', newUrl);
return currentDomain === newDomain; return currentDomain === newDomain;
} }
function shouldInjectCss() { function shouldInjectCss() {
try { try {
fs.accessSync(INJECT_CSS_PATH, fs.F_OK); fs.accessSync(INJECT_CSS_PATH, fs.F_OK);
return true; return true;
} catch (e) { } catch (e) {
return false; return false;
} }
} }
function getCssToInject() { function getCssToInject() {
return fs.readFileSync(INJECT_CSS_PATH).toString(); return fs.readFileSync(INJECT_CSS_PATH).toString();
} }
/** /**
@ -47,19 +47,20 @@ function getCssToInject() {
* @param message * @param message
*/ */
function debugLog(browserWindow, message) { function debugLog(browserWindow, message) {
// need the timeout as it takes time for the preload javascript to be loaded in the window // need the timeout as it takes time for the preload javascript to be loaded in the window
setTimeout(() => { setTimeout(() => {
browserWindow.webContents.send('debug', message); browserWindow.webContents.send('debug', message);
}, 3000); }, 3000);
console.log(message); // eslint-disable-next-line no-console
console.log(message);
} }
export default { export default {
isOSX, isOSX,
isLinux, isLinux,
isWindows, isWindows,
linkIsInternal, linkIsInternal,
getCssToInject, getCssToInject,
debugLog, debugLog,
shouldInjectCss shouldInjectCss,
}; };

View File

@ -2,23 +2,7 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
import helpers from './helpers'; import helpers from './helpers';
const {isOSX, isWindows, isLinux} = 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');
}
/** /**
* Synchronously find a file or directory * 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 * @param {boolean} [findDir] if true, search results will be limited to only directories
* @returns {Array} * @returns {Array}
*/ */
function findSync(pattern, base, findDir) { function findSync(pattern, basePath, findDir) {
const matches = []; const matches = [];
(function findSyncRecurse(base) { (function findSyncRecurse(base) {
let children; let children;
try { try {
children = fs.readdirSync(base); children = fs.readdirSync(base);
} catch (exception) { } catch (exception) {
if (exception.code === 'ENOENT') { if (exception.code === 'ENOENT') {
return; return;
} }
throw exception; 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 => { if (!findDir) {
const childPath = path.join(base, child); matches.push(childPath);
const childIsDirectory = fs.lstatSync(childPath).isDirectory(); return;
const patternMatches = pattern.test(childPath); }
if (!patternMatches) { if (childIsDirectory) {
if (!childIsDirectory) { matches.push(childPath);
return; }
} });
findSyncRecurse(childPath); }(basePath));
return; return matches;
}
if (!findDir) {
matches.push(childPath);
return;
}
if (childIsDirectory) {
matches.push(childPath);
}
});
})(base);
return matches;
} }
function linuxMatch() { function linuxMatch() {
return findSync(/libpepflashplayer\.so/, '/opt/google/chrome')[0]; return findSync(/libpepflashplayer\.so/, '/opt/google/chrome')[0];
} }
function windowsMatch() { 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() { 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; export default inferFlash;

View File

@ -1,14 +1,15 @@
import 'source-map-support/register'; import 'source-map-support/register';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; 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 createLoginWindow from './components/login/loginWindow';
import createMainWindow from './components/mainWindow/mainWindow'; import createMainWindow from './components/mainWindow/mainWindow';
import helpers from './helpers/helpers'; import helpers from './helpers/helpers';
import inferFlash from './helpers/inferFlash'; import inferFlash from './helpers/inferFlash';
import electronDownload from 'electron-dl';
const {isOSX} = helpers; const { isOSX } = helpers;
electronDownload(); electronDownload();
@ -18,83 +19,82 @@ const appArgs = JSON.parse(fs.readFileSync(APP_ARGS_FILE_PATH, 'utf8'));
let mainWindow; let mainWindow;
if (typeof appArgs.flashPluginDir === 'string') { 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) { } else if (appArgs.flashPluginDir) {
const flashPath = inferFlash(); const flashPath = inferFlash();
app.commandLine.appendSwitch('ppapi-flash-path', flashPath); app.commandLine.appendSwitch('ppapi-flash-path', flashPath);
} }
if (appArgs.ignoreCertificate) { if (appArgs.ignoreCertificate) {
app.commandLine.appendSwitch('ignore-certificate-errors'); app.commandLine.appendSwitch('ignore-certificate-errors');
} }
// do nothing for setDockBadge if not OSX // do nothing for setDockBadge if not OSX
let setDockBadge = () => {}; let setDockBadge = () => {};
if (isOSX()) { if (isOSX()) {
setDockBadge = app.dock.setBadge; setDockBadge = app.dock.setBadge;
} }
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
if (!isOSX() || appArgs.fastQuit) { if (!isOSX() || appArgs.fastQuit) {
app.quit(); app.quit();
} }
}); });
app.on('activate', (event, hasVisibleWindows) => { app.on('activate', (event, hasVisibleWindows) => {
if (isOSX()) { if (isOSX()) {
// this is called when the dock is clicked // this is called when the dock is clicked
if (!hasVisibleWindows) { if (!hasVisibleWindows) {
mainWindow.show(); mainWindow.show();
}
} }
}
}); });
app.on('before-quit', () => { app.on('before-quit', () => {
// not fired when the close button on the window is clicked // not fired when the close button on the window is clicked
if (isOSX()) { if (isOSX()) {
// need to force a quit as a workaround here to simulate the osx app hiding behaviour // 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, // Somehow sokution at https://github.com/atom/electron/issues/444#issuecomment-76492576 does not work,
// e.prevent default appears to persist // e.prevent default appears to persist
// might cause issues in the future as before-quit and will-quit events are not called // might cause issues in the future as before-quit and will-quit events are not called
app.exit(0); app.exit(0);
} }
}); });
if (appArgs.crashReporter) { if (appArgs.crashReporter) {
app.on('will-finish-launching', () => { app.on('will-finish-launching', () => {
crashReporter.start({ crashReporter.start({
productName: appArgs.name, productName: appArgs.name,
submitURL: appArgs.crashReporter, submitURL: appArgs.crashReporter,
autoSubmit: true autoSubmit: true,
});
}); });
});
} }
app.on('ready', () => { app.on('ready', () => {
mainWindow = createMainWindow(appArgs, app.quit, setDockBadge); mainWindow = createMainWindow(appArgs, app.quit, setDockBadge);
}); });
app.on('login', (event, webContents, request, authInfo, callback) => { app.on('login', (event, webContents, request, authInfo, callback) => {
// for http authentication // for http authentication
event.preventDefault(); event.preventDefault();
createLoginWindow(callback); createLoginWindow(callback);
}); });
if (appArgs.singleInstance) { if (appArgs.singleInstance) {
const shouldQuit = app.makeSingleInstance(() => { const shouldQuit = app.makeSingleInstance(() => {
// Someone tried to run a second instance, we should focus our window. // Someone tried to run a second instance, we should focus our window.
if (mainWindow) { if (mainWindow) {
if (mainWindow.isMinimized()) { if (mainWindow.isMinimized()) {
mainWindow.restore(); mainWindow.restore();
} }
mainWindow.focus(); mainWindow.focus();
}
});
if (shouldQuit) {
app.quit();
} }
});
if (shouldQuit) {
app.quit();
}
} }

View File

@ -0,0 +1,2 @@
env:
browser: true

View File

@ -1,11 +1,12 @@
import electron from 'electron'; import electron from 'electron';
const {ipcRenderer} = electron;
const { ipcRenderer } = electron;
const form = document.getElementById('login-form'); const form = document.getElementById('login-form');
form.addEventListener('submit', event => { form.addEventListener('submit', (event) => {
event.preventDefault(); event.preventDefault();
const username = document.getElementById('username-input').value; const username = document.getElementById('username-input').value;
const password = document.getElementById('password-input').value; const password = document.getElementById('password-input').value;
ipcRenderer.send('login-message', [username, password]); ipcRenderer.send('login-message', [username, password]);
}); });

View File

@ -1,86 +1,85 @@
/** /**
Preload file that will be executed in the renderer process 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 path from 'path';
import fs from 'fs'; import fs from 'fs';
const INJECT_JS_PATH = path.join(__dirname, '../../', 'inject/inject.js'); 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 <a> bearing the href
// for example, <a href="..."><span>Google</span></a>
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 * Patches window.Notification to set a callback on a new Notification
* @param callback * @param callback
*/ */
function setNotificationCallback(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; window.Notification = newNotify;
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;
} }
function clickSelector(element) { function clickSelector(element) {
const mouseEvent = new MouseEvent('click'); const mouseEvent = new MouseEvent('click');
element.dispatchEvent(mouseEvent); element.dispatchEvent(mouseEvent);
} }
function injectScripts() { function injectScripts() {
const needToInject = fs.existsSync(INJECT_JS_PATH); const needToInject = fs.existsSync(INJECT_JS_PATH);
if (!needToInject) { if (!needToInject) {
return; return;
} }
require(INJECT_JS_PATH); // 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 <a> bearing the href
// for example, <a href="..."><span>Google</span></a>
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);
});

View File

@ -1,19 +1,18 @@
import gulp from 'gulp'; import gulp from 'gulp';
import PATHS from './helpers/src-paths';
import del from 'del'; import del from 'del';
import runSequence from 'run-sequence'; import runSequence from 'run-sequence';
import PATHS from './helpers/src-paths';
gulp.task('build', callback => { gulp.task('build', (callback) => {
runSequence('clean', ['build-cli', 'build-app', 'build-tests'], callback); runSequence('clean', ['build-cli', 'build-app', 'build-tests'], callback);
}); });
gulp.task('clean', callback => { gulp.task('clean', (callback) => {
del(PATHS.CLI_DEST).then(() => { del(PATHS.CLI_DEST).then(() => {
del(PATHS.APP_DEST).then(() => { del(PATHS.APP_DEST).then(() => {
del(PATHS.TEST_DEST).then(() => { del(PATHS.TEST_DEST).then(() => {
callback(); callback();
}); });
});
}); });
});
}); });

View File

@ -1,10 +1,9 @@
import gulp from 'gulp'; import gulp from 'gulp';
import webpack from 'webpack-stream';
import PATHS from './../helpers/src-paths'; import PATHS from './../helpers/src-paths';
import webpack from 'webpack-stream'; const webpackConfig = require('./../../webpack.config.js');
gulp.task('build-app', ['build-static'], () => { gulp.task('build-app', ['build-static'], () => gulp.src(PATHS.APP_MAIN_JS)
return gulp.src(PATHS.APP_MAIN_JS) .pipe(webpack(webpackConfig))
.pipe(webpack(require('./../../webpack.config.js'))) .pipe(gulp.dest(PATHS.APP_DEST)));
.pipe(gulp.dest(PATHS.APP_DEST));
});

View File

@ -2,8 +2,6 @@ import gulp from 'gulp';
import PATHS from './../helpers/src-paths'; import PATHS from './../helpers/src-paths';
import helpers from './../helpers/gulp-helpers'; import helpers from './../helpers/gulp-helpers';
const {buildES6} = helpers; const { buildES6 } = helpers;
gulp.task('build-cli', done => { gulp.task('build-cli', done => buildES6(PATHS.CLI_SRC_JS, PATHS.CLI_DEST, done));
return buildES6(PATHS.CLI_SRC_JS, PATHS.CLI_DEST, done);
});

View File

@ -2,15 +2,11 @@ import gulp from 'gulp';
import PATHS from './../helpers/src-paths'; import PATHS from './../helpers/src-paths';
import helpers from './../helpers/gulp-helpers'; import helpers from './../helpers/gulp-helpers';
const {buildES6} = helpers; const { buildES6 } = helpers;
gulp.task('build-static-not-js', () => { gulp.task('build-static-not-js', () => gulp.src([PATHS.APP_STATIC_ALL, '!**/*.js'])
return gulp.src([PATHS.APP_STATIC_ALL, '!**/*.js']) .pipe(gulp.dest(PATHS.APP_STATIC_DEST)));
.pipe(gulp.dest(PATHS.APP_STATIC_DEST));
});
gulp.task('build-static-js', done => { gulp.task('build-static-js', done => buildES6(PATHS.APP_STATIC_JS, PATHS.APP_STATIC_DEST, done));
return buildES6(PATHS.APP_STATIC_JS, PATHS.APP_STATIC_DEST, done);
});
gulp.task('build-static', ['build-static-js', 'build-static-not-js']); gulp.task('build-static', ['build-static-js', 'build-static-not-js']);

View File

@ -4,17 +4,17 @@ import sourcemaps from 'gulp-sourcemaps';
import babel from 'gulp-babel'; import babel from 'gulp-babel';
function shellExec(cmd, silent, callback) { function shellExec(cmd, silent, callback) {
shellJs.exec(cmd, {silent: silent}, (code, stdout, stderr) => { shellJs.exec(cmd, { silent }, (code, stdout, stderr) => {
if (code) { if (code) {
callback(JSON.stringify({code, stdout, stderr})); callback(JSON.stringify({ code, stdout, stderr }));
return; return;
} }
callback(); callback();
}); });
} }
function buildES6(src, dest, callback) { function buildES6(src, dest, callback) {
return gulp.src(src) return gulp.src(src)
.pipe(sourcemaps.init()) .pipe(sourcemaps.init())
.pipe(babel()) .pipe(babel())
.on('error', callback) .on('error', callback)
@ -23,6 +23,6 @@ function buildES6(src, dest, callback) {
} }
export default { export default {
shellExec, shellExec,
buildES6 buildES6,
}; };

View File

@ -1,22 +1,22 @@
import path from 'path'; import path from 'path';
const paths = { const paths = {
APP_SRC: 'app/src', APP_SRC: 'app/src',
APP_DEST: 'app/lib', APP_DEST: 'app/lib',
CLI_SRC: 'src', CLI_SRC: 'src',
CLI_DEST: 'lib', CLI_DEST: 'lib',
TEST_SRC: 'test', TEST_SRC: 'test',
TEST_DEST: 'built-tests' TEST_DEST: 'built-tests',
}; };
paths.APP_MAIN_JS = path.join(paths.APP_SRC, '/main.js'); paths.APP_MAIN_JS = path.join(paths.APP_SRC, '/main.js');
paths.APP_ALL = paths.APP_SRC + '/**/*'; paths.APP_ALL = `${paths.APP_SRC}/**/*`;
paths.APP_STATIC_ALL = path.join(paths.APP_SRC, 'static') + '/**/*'; paths.APP_STATIC_ALL = `${path.join(paths.APP_SRC, 'static')}/**/*`;
paths.APP_STATIC_JS = path.join(paths.APP_SRC, 'static') + '/**/*.js'; paths.APP_STATIC_JS = `${path.join(paths.APP_SRC, 'static')}/**/*.js`;
paths.APP_STATIC_DEST = path.join(paths.APP_DEST, 'static'); paths.APP_STATIC_DEST = path.join(paths.APP_DEST, 'static');
paths.CLI_SRC_JS = paths.CLI_SRC + '/**/*.js'; paths.CLI_SRC_JS = `${paths.CLI_SRC}/**/*.js`;
paths.CLI_DEST_JS = paths.CLI_DEST + '/**/*.js'; paths.CLI_DEST_JS = `${paths.CLI_DEST}/**/*.js`;
paths.TEST_SRC_JS = paths.TEST_SRC + '/**/*.js'; paths.TEST_SRC_JS = `${paths.TEST_SRC}/**/*.js`;
paths.TEST_DEST_JS = paths.TEST_DEST + '/**/*.js'; paths.TEST_DEST_JS = `${paths.TEST_DEST}/**/*.js`;
export default paths; export default paths;

View File

@ -2,12 +2,10 @@ import gulp from 'gulp';
import runSequence from 'run-sequence'; import runSequence from 'run-sequence';
import helpers from './helpers/gulp-helpers'; import helpers from './helpers/gulp-helpers';
const {shellExec} = helpers; const { shellExec } = helpers;
gulp.task('publish', done => { gulp.task('publish', (done) => {
shellExec('npm publish', false, done); shellExec('npm publish', false, done);
}); });
gulp.task('release', callback => { gulp.task('release', callback => runSequence('build', 'publish', callback));
return runSequence('build', 'publish', callback);
});

View File

@ -2,12 +2,10 @@ import gulp from 'gulp';
import runSequence from 'run-sequence'; import runSequence from 'run-sequence';
import helpers from './helpers/gulp-helpers'; import helpers from './helpers/gulp-helpers';
const {shellExec} = helpers; const { shellExec } = helpers;
gulp.task('prune', done => { gulp.task('prune', (done) => {
shellExec('npm prune', true, done); shellExec('npm prune', true, done);
}); });
gulp.task('test', callback => { gulp.task('test', callback => runSequence('prune', 'mocha', callback));
return runSequence('prune', 'mocha', callback);
});

View File

@ -2,8 +2,6 @@ import gulp from 'gulp';
import PATHS from './../helpers/src-paths'; import PATHS from './../helpers/src-paths';
import helpers from './../helpers/gulp-helpers'; import helpers from './../helpers/gulp-helpers';
const {buildES6} = helpers; const { buildES6 } = helpers;
gulp.task('build-tests', done => { gulp.task('build-tests', done => buildES6(PATHS.TEST_SRC_JS, PATHS.TEST_DEST, done));
return buildES6(PATHS.TEST_SRC_JS, PATHS.TEST_DEST, done);
});

View File

@ -1,32 +1,27 @@
import gulp from 'gulp'; 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 PATHS from './../helpers/src-paths';
import istanbul from 'gulp-istanbul'; gulp.task('mocha', (done) => {
import {Instrumenter} from 'isparta'; gulp.src([PATHS.CLI_SRC_JS, '!src/cli.js'])
import mocha from 'gulp-mocha'; .pipe(istanbul({
instrumenter: Instrumenter,
gulp.task('mocha', done => { includeUntested: true,
gulp.src([PATHS.CLI_SRC_JS, '!src/cli.js']) }))
.pipe(istanbul({ .pipe(istanbul.hookRequire()) // Force `require` to return covered files
instrumenter: Instrumenter, .on('finish', () => gulp.src(PATHS.TEST_SRC, { read: false })
includeUntested: true .pipe(mocha({
})) compilers: 'js:babel-core/register',
.pipe(istanbul.hookRequire()) // Force `require` to return covered files recursive: true,
.on('finish', () => { }))
return gulp.src(PATHS.TEST_SRC, {read: false}) .pipe(istanbul.writeReports({
.pipe(mocha({ dir: './coverage',
compilers: 'js:babel-core/register', reporters: ['lcov'],
recursive: true reportOpts: { dir: './coverage' },
})) }))
.pipe(istanbul.writeReports({ .on('end', done));
dir: './coverage',
reporters: ['lcov'],
reportOpts: {dir: './coverage'}
}))
.on('end', done);
});
}); });
gulp.task('tdd', ['mocha'], () => { gulp.task('tdd', ['mocha'], () => gulp.watch(['src/**/*.js', 'test/**/*.js'], ['mocha']));
return gulp.watch(['src/**/*.js', 'test/**/*.js'], ['mocha']);
});

View File

@ -2,15 +2,15 @@ import gulp from 'gulp';
import PATHS from './helpers/src-paths'; import PATHS from './helpers/src-paths';
gulp.task('watch', ['build'], () => { gulp.task('watch', ['build'], () => {
var handleError = function(error) { const handleError = function (error) {
console.error(error); console.error(error);
}; };
gulp.watch(PATHS.APP_ALL, ['build-app']) gulp.watch(PATHS.APP_ALL, ['build-app'])
.on('error', handleError); .on('error', handleError);
gulp.watch(PATHS.CLI_SRC_JS, ['build-cli']) gulp.watch(PATHS.CLI_SRC_JS, ['build-cli'])
.on('error', handleError); .on('error', handleError);
gulp.watch(PATHS.TEST_SRC_JS, ['build-tests']) gulp.watch(PATHS.TEST_SRC_JS, ['build-tests'])
.on('error', handleError); .on('error', handleError);
}); });

View File

@ -2,8 +2,8 @@ import gulp from 'gulp';
import requireDir from 'require-dir'; import requireDir from 'require-dir';
requireDir('./gulp', { requireDir('./gulp', {
recurse: true, recurse: true,
duplicates: true duplicates: true,
}); });
gulp.task('default', ['build']); gulp.task('default', ['build']);

View File

@ -15,6 +15,7 @@
"test": "gulp test", "test": "gulp test",
"tdd": "gulp tdd", "tdd": "gulp tdd",
"lint": "eslint .", "lint": "eslint .",
"lint:fix": "eslint . --fix",
"ci": "gulp build test && npm run lint", "ci": "gulp build test && npm run lint",
"clean": "gulp clean", "clean": "gulp clean",
"build": "gulp build", "build": "gulp build",
@ -68,8 +69,9 @@
"babel-register": "^6.6.0", "babel-register": "^6.6.0",
"chai": "^3.4.1", "chai": "^3.4.1",
"del": "^2.2.0", "del": "^2.2.0",
"eslint": "^2.10.2", "eslint": "^3.19.0",
"eslint-config-google": "^0.5.0", "eslint-config-airbnb-base": "^11.1.3",
"eslint-plugin-import": "^2.2.0",
"gulp": "^3.9.0", "gulp": "^3.9.0",
"gulp-babel": "^6.1.1", "gulp-babel": "^6.1.1",
"gulp-istanbul": "^1.1.1", "gulp-istanbul": "^1.1.1",

View File

@ -7,8 +7,100 @@ import ncp from 'ncp';
const copy = ncp.ncp; const copy = ncp.ncp;
/** /**
* Creates a temporary directory and copies the './app folder' inside, and adds a text file with the configuration * Only picks certain app args to pass to nativefier.json
* for the single page app. * @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} src
* @param {string} dest * @param {string} dest
@ -16,119 +108,25 @@ const copy = ncp.ncp;
* @param callback * @param callback
*/ */
function buildApp(src, dest, options, callback) { function buildApp(src, dest, options, callback) {
const appArgs = selectAppArgs(options); const appArgs = selectAppArgs(options);
copy(src, dest, error => { copy(src, dest, (error) => {
if (error) { if (error) {
callback(`Error Copying temporary directory: ${error}`); callback(`Error Copying temporary directory: ${error}`);
return; 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 promises = srcs.map(src => {
return new Promise((resolve, reject) => {
if (!fs.existsSync(src)) {
reject('Error copying injection files: file not found');
return;
}
let destFileName; fs.writeFileSync(path.join(dest, '/nativefier.json'), JSON.stringify(appArgs));
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 => { maybeCopyScripts(options.inject, dest)
if (error) { .catch((error) => {
reject(`Error Copying injection files: ${error}`); console.warn(error);
return; })
} .then(() => {
resolve(); changeAppPackageJsonName(dest, appArgs.name, appArgs.targetUrl);
}); callback();
}); });
}); });
return new Promise((resolve, reject) => {
Promise.all(promises)
.then(() => {
resolve();
})
.catch(error => {
reject(error);
});
});
} }
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; export default buildApp;

View File

@ -15,118 +15,39 @@ import buildApp from './buildApp';
const copy = ncp.ncp; const copy = ncp.ncp;
const isWindows = helpers.isWindows; 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 * Checks the app path array to determine if the packaging was completed successfully
* @param appPathArray Result from electron-packager * @param appPathArray Result from electron-packager
* @returns {*} * @returns {*}
*/ */
function getAppPath(appPathArray) { function getAppPath(appPathArray) {
if (appPathArray.length === 0) { if (appPathArray.length === 0) {
// directory already exists, --overwrite is not set // directory already exists, --overwrite is not set
// exit here // exit here
return null; return null;
} }
if (appPathArray.length > 1) { if (appPathArray.length > 1) {
log.warn('Warning: This should not be happening, packaged app path contains more than one element:', appPathArray); 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 * @param options
*/ */
function maybeNoIconOption(options) { function maybeNoIconOption(options) {
const packageOptions = JSON.parse(JSON.stringify(options)); const packageOptions = JSON.parse(JSON.stringify(options));
if (options.platform === 'win32' && !isWindows()) { if (options.platform === 'win32' && !isWindows()) {
if (!hasBinary.sync('wine')) { if (!hasBinary.sync('wine')) {
log.warn('Wine is required to set the icon for a Windows app when packaging on non-windows platforms'); log.warn('Wine is required to set the icon for a Windows app when packaging on non-windows platforms');
packageOptions.icon = null; packageOptions.icon = null;
}
} }
return packageOptions; }
return packageOptions;
} }
/** /**
@ -137,23 +58,105 @@ function maybeNoIconOption(options) {
* @param callback * @param callback
*/ */
function maybeCopyIcons(options, appPath, 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(); callback();
return; return;
} }
if (options.platform === 'darwin') { maybeCopyIcons(options, appPath, (error) => {
callback(); callback(error, appPath);
return; });
} },
], (error, appPath) => {
// windows & linux packagerConsole.playback();
// put the icon file into the app callback(error, appPath);
const destIconPath = path.join(appPath, 'resources/app'); });
const destFileName = `icon${path.extname(options.icon)}`;
copy(options.icon, path.join(destIconPath, destFileName), error => {
callback(error);
});
} }
export default buildMain; export default buildMain;

View File

@ -3,8 +3,20 @@ import log from 'loglevel';
import helpers from './../helpers/helpers'; import helpers from './../helpers/helpers';
import iconShellHelpers from './../helpers/iconShellHelpers'; import iconShellHelpers from './../helpers/iconShellHelpers';
const {isOSX} = helpers; const { isOSX } = helpers;
const {convertToPng, convertToIco, convertToIcns} = iconShellHelpers; 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 * @callback augmentIconsCallback
@ -16,88 +28,76 @@ const {convertToPng, convertToIco, convertToIcns} = iconShellHelpers;
* Will check and convert a `.png` to `.icns` if necessary and augment * Will check and convert a `.png` to `.icns` if necessary and augment
* options.icon with the result * 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 * @param {augmentIconsCallback} callback
*/ */
function iconBuild(options, callback) { function iconBuild(inpOptions, callback) {
const options = Object.assign({}, inpOptions);
const returnCallback = () => {
callback(null, options);
};
const returnCallback = () => { if (!options.icon) {
callback(null, options); returnCallback();
}; return;
}
if (!options.icon) { if (options.platform === 'win32') {
if (iconIsIco(options.icon)) {
returnCallback();
return;
}
convertToIco(options.icon)
.then((outPath) => {
options.icon = outPath;
returnCallback(); returnCallback();
return; })
} .catch((error) => {
log.warn('Skipping icon conversion to .ico', error);
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)) {
returnCallback(); returnCallback();
return; });
return;
}
if (options.platform === 'linux') {
if (iconIsPng(options.icon)) {
returnCallback();
return;
} }
if (!isOSX()) { convertToPng(options.icon)
log.warn('Skipping icon conversion to .icns, conversion is only supported on OSX'); .then((outPath) => {
options.icon = outPath;
returnCallback(); returnCallback();
return; })
} .catch((error) => {
log.warn('Skipping icon conversion to .png', error);
returnCallback();
});
return;
}
convertToIcns(options.icon) if (iconIsIcns(options.icon)) {
.then(outPath => { returnCallback();
options.icon = outPath; return;
returnCallback(); }
})
.catch(error => {
log.warn('Skipping icon conversion to .icns', error);
returnCallback();
});
}
function iconIsIco(iconPath) { if (!isOSX()) {
return path.extname(iconPath) === '.ico'; log.warn('Skipping icon conversion to .icns, conversion is only supported on OSX');
} returnCallback();
return;
}
function iconIsPng(iconPath) { convertToIcns(options.icon)
return path.extname(iconPath) === '.png'; .then((outPath) => {
} options.icon = outPath;
returnCallback();
function iconIsIcns(iconPath) { })
return path.extname(iconPath) === '.icns'; .catch((error) => {
log.warn('Skipping icon conversion to .icns', error);
returnCallback();
});
} }
export default iconBuild; export default iconBuild;

View File

@ -1,75 +1,73 @@
#! /usr/bin/env node #! /usr/bin/env node
import 'source-map-support/register'; import 'source-map-support/register';
import path from 'path';
import program from 'commander'; import program from 'commander';
import nativefier from './index'; import nativefier from './index';
const packageJson = require(path.join('..', 'package'));
const packageJson = require('./../package');
function collect(val, memo) { function collect(val, memo) {
memo.push(val); memo.push(val);
return memo; return memo;
} }
if (require.main === module) { if (require.main === module) {
program
.version(packageJson.version)
.arguments('<targetUrl> [dest]')
.action((targetUrl, appDir) => {
program.targetUrl = targetUrl;
program.out = appDir;
})
.option('-n, --name <value>', 'app name')
.option('-p, --platform <value>', '\'osx\', \'linux\' or \'windows\'')
.option('-a, --arch <value>', '\'ia32\' or \'x64\'')
.option('-e, --electron-version <value>', '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 <value>', '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 <value>', 'set window default width, defaults to 1280px', parseInt)
.option('--height <value>', 'set window default height, defaults to 800px', parseInt)
.option('--min-width <value>', 'set window minimum width, defaults to 0px', parseInt)
.option('--min-height <value>', 'set window minimum height, defaults to 0px', parseInt)
.option('--max-width <value>', 'set window maximum width, default is no limit', parseInt)
.option('--max-height <value>', '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 <value>', '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 <value>', 'path to Chrome flash plugin, find it in `Chrome://plugins`')
.option('--inject <value>', '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 <value>', 'default zoom factor to use when the app is opened, defaults to 1.0', parseFloat)
.option('--internal-urls <value>', '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 <value>', 'remote server URL to send crash reports')
.option('--single-instance', 'allow only a single instance of the application')
.parse(process.argv);
program if (!process.argv.slice(2).length) {
.version(packageJson.version) program.help();
.arguments('<targetUrl> [dest]') }
.action(function(targetUrl, appDir) {
program.targetUrl = targetUrl;
program.out = appDir;
})
.option('-n, --name <value>', 'app name')
.option('-p, --platform <value>', '\'osx\', \'linux\' or \'windows\'')
.option('-a, --arch <value>', '\'ia32\' or \'x64\'')
.option('-e, --electron-version <value>', '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 <value>', '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 <value>', 'set window default width, defaults to 1280px', parseInt)
.option('--height <value>', 'set window default height, defaults to 800px', parseInt)
.option('--min-width <value>', 'set window minimum width, defaults to 0px', parseInt)
.option('--min-height <value>', 'set window minimum height, defaults to 0px', parseInt)
.option('--max-width <value>', 'set window maximum width, default is no limit', parseInt)
.option('--max-height <value>', '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 <value>', '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 <value>', 'path to Chrome flash plugin, find it in `Chrome://plugins`')
.option('--inject <value>', '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 <value>', 'default zoom factor to use when the app is opened, defaults to 1.0', parseFloat)
.option('--internal-urls <value>', '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 <value>', '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) { nativefier(program, (error, appPath) => {
program.help(); if (error) {
console.error(error);
return;
} }
nativefier(program, (error, appPath) => { if (!appPath) {
if (error) { // app exists and --overwrite is not passed
console.error(error); return;
return; }
} console.log(`App built to ${appPath}`);
});
if (!appPath) {
// app exists and --overwrite is not passed
return;
}
console.log(`App built to ${appPath}`);
});
} }

View File

@ -2,6 +2,7 @@ import shell from 'shelljs';
import path from 'path'; import path from 'path';
import tmp from 'tmp'; import tmp from 'tmp';
import helpers from './helpers'; import helpers from './helpers';
const isOSX = helpers.isOSX; const isOSX = helpers.isOSX;
tmp.setGracefulCleanup(); tmp.setGracefulCleanup();
@ -20,27 +21,27 @@ const PNG_TO_ICNS_BIN_PATH = path.join(__dirname, '../..', 'bin/convertToIcns');
* @param {pngToIcnsCallback} callback * @param {pngToIcnsCallback} callback
*/ */
function convertToIcns(pngSrc, icnsDest, callback) { function convertToIcns(pngSrc, icnsDest, callback) {
if (!isOSX()) { if (!isOSX()) {
callback('OSX is required to convert .png to .icns icon', pngSrc); 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; return;
}
callback(stdOut, pngSrc);
return;
} }
shell.exec(`${PNG_TO_ICNS_BIN_PATH} ${pngSrc} ${icnsDest}`, {silent: true}, (exitCode, stdOut, stdError) => { callback(null, icnsDest);
if (stdOut.includes('icon.iconset:error') || exitCode) { });
if (exitCode) {
callback({
stdOut: stdOut,
stdError: stdError
}, pngSrc);
return;
}
callback(stdOut, pngSrc);
return;
}
callback(null, icnsDest);
});
} }
/** /**
@ -49,9 +50,9 @@ function convertToIcns(pngSrc, icnsDest, callback) {
* @param {pngToIcnsCallback} callback * @param {pngToIcnsCallback} callback
*/ */
function convertToIcnsTmp(pngSrc, callback) { function convertToIcnsTmp(pngSrc, callback) {
const tempIconDirObj = tmp.dirSync({unsafeCleanup: true}); const tempIconDirObj = tmp.dirSync({ unsafeCleanup: true });
const tempIconDirPath = tempIconDirObj.name; const tempIconDirPath = tempIconDirObj.name;
convertToIcns(pngSrc, `${tempIconDirPath}/icon.icns`, callback); convertToIcns(pngSrc, `${tempIconDirPath}/icon.icns`, callback);
} }
export default convertToIcnsTmp; export default convertToIcnsTmp;

View File

@ -1,68 +1,70 @@
import ProgressBar from 'progress'; import ProgressBar from 'progress';
class DishonestProgress { class DishonestProgress {
constructor(total) { constructor(total) {
this.tickParts = total * 10; this.tickParts = total * 10;
this.bar = new ProgressBar(' :task [:bar] :percent', { this.bar = new ProgressBar(' :task [:bar] :percent', {
complete: '=', complete: '=',
incomplete: ' ', incomplete: ' ',
total: total * this.tickParts, total: total * this.tickParts,
width: 50, width: 50,
clear: true clear: true,
}); });
this.tickingPrevious = { this.tickingPrevious = {
message: '', message: '',
remainder: 0, remainder: 0,
interval: null 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 realRemainder = this.bar.total - this.bar.curr;
if (realRemainder === this.tickParts) {
const {remainder: prevRemainder, message: prevMessage, interval: prevInterval} = this.tickingPrevious; this.bar.tick(this.tickParts, {
task: message,
if (prevRemainder) { });
this.bar.tick(prevRemainder, { return;
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);
} }
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; export default DishonestProgress;

View File

@ -4,100 +4,100 @@ import hasBinary from 'hasbin';
import path from 'path'; import path from 'path';
function isOSX() { function isOSX() {
return os.platform() === 'darwin'; return os.platform() === 'darwin';
} }
function isWindows() { function isWindows() {
return os.platform() === 'win32'; return os.platform() === 'win32';
} }
function downloadFile(fileUrl) { function downloadFile(fileUrl) {
return axios.get( return axios.get(
fileUrl, { fileUrl, {
responseType: 'arraybuffer' responseType: 'arraybuffer',
}) })
.then(function(response) { .then((response) => {
if (!response.data) { if (!response.data) {
return null; return null;
} }
return { return {
data: response.data, data: response.data,
ext: path.extname(fileUrl) ext: path.extname(fileUrl),
}; };
}); });
} }
function allowedIconFormats(platform) { function allowedIconFormats(platform) {
const hasIdentify = hasBinary.sync('identify'); const hasIdentify = hasBinary.sync('identify');
const hasConvert = hasBinary.sync('convert'); const hasConvert = hasBinary.sync('convert');
const hasIconUtil = hasBinary.sync('iconutil'); const hasIconUtil = hasBinary.sync('iconutil');
const pngToIcns = hasConvert && hasIconUtil; const pngToIcns = hasConvert && hasIconUtil;
const pngToIco = hasConvert; const pngToIco = hasConvert;
const icoToIcns = pngToIcns && hasIdentify; const icoToIcns = pngToIcns && hasIdentify;
const icoToPng = hasConvert; const icoToPng = hasConvert;
// todo scripts for the following // todo scripts for the following
const icnsToPng = false; const icnsToPng = false;
const icnsToIco = false; const icnsToIco = false;
const formats = []; 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;
}
// todo shell scripting is not supported on windows, temporary override
if (isWindows()) {
switch (platform) { switch (platform) {
case 'darwin': case 'darwin':
formats.push('.icns'); formats.push('.icns');
if (pngToIcns) { break;
formats.push('.png'); case 'linux':
} formats.push('.png');
if (icoToIcns) { break;
formats.push('.ico'); case 'win32':
} formats.push('.ico');
break; break;
case 'linux': default:
formats.push('.png'); throw new Error(`function allowedIconFormats error: Unknown platform ${platform}`);
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}`;
} }
return formats; 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 { export default {
isOSX, isOSX,
isWindows, isWindows,
downloadFile, downloadFile,
allowedIconFormats allowedIconFormats,
}; };

View File

@ -2,15 +2,16 @@ import shell from 'shelljs';
import path from 'path'; import path from 'path';
import tmp from 'tmp'; import tmp from 'tmp';
import helpers from './helpers'; import helpers from './helpers';
const {isWindows, isOSX} = helpers;
const { isWindows, isOSX } = helpers;
tmp.setGracefulCleanup(); tmp.setGracefulCleanup();
const SCRIPT_PATHS = { const SCRIPT_PATHS = {
singleIco: path.join(__dirname, '../..', 'bin/singleIco'), singleIco: path.join(__dirname, '../..', 'bin/singleIco'),
convertToPng: path.join(__dirname, '../..', 'bin/convertToPng'), convertToPng: path.join(__dirname, '../..', 'bin/convertToPng'),
convertToIco: path.join(__dirname, '../..', 'bin/convertToIco'), convertToIco: path.join(__dirname, '../..', 'bin/convertToIco'),
convertToIcns: path.join(__dirname, '../..', 'bin/convertToIcns') convertToIcns: path.join(__dirname, '../..', 'bin/convertToIcns'),
}; };
/** /**
@ -20,29 +21,29 @@ const SCRIPT_PATHS = {
* @param {string} dest has to be a .ico path * @param {string} dest has to be a .ico path
*/ */
function iconShellHelper(shellScriptPath, icoSrc, dest) { function iconShellHelper(shellScriptPath, icoSrc, dest) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (isWindows()) { if (isWindows()) {
reject('OSX or Linux is required'); reject('OSX or Linux is required');
return; return;
} }
shell.exec(`${shellScriptPath} ${icoSrc} ${dest}`, {silent: true}, (exitCode, stdOut, stdError) => { shell.exec(`${shellScriptPath} ${icoSrc} ${dest}`, { silent: true }, (exitCode, stdOut, stdError) => {
if (exitCode) { if (exitCode) {
reject({ reject({
stdOut: stdOut, stdOut,
stdError: stdError stdError,
});
return;
}
resolve(dest);
}); });
return;
}
resolve(dest);
}); });
});
} }
function getTmpDirPath() { function getTmpDirPath() {
const tempIconDirObj = tmp.dirSync({unsafeCleanup: true}); const tempIconDirObj = tmp.dirSync({ unsafeCleanup: true });
return tempIconDirObj.name; return tempIconDirObj.name;
} }
/** /**
@ -52,27 +53,27 @@ function getTmpDirPath() {
*/ */
function singleIco(icoSrc) { function singleIco(icoSrc) {
return iconShellHelper(SCRIPT_PATHS.singleIco, icoSrc, `${getTmpDirPath()}/icon.ico`); return iconShellHelper(SCRIPT_PATHS.singleIco, icoSrc, `${getTmpDirPath()}/icon.ico`);
} }
function convertToPng(icoSrc) { function convertToPng(icoSrc) {
return iconShellHelper(SCRIPT_PATHS.convertToPng, icoSrc, `${getTmpDirPath()}/icon.png`); return iconShellHelper(SCRIPT_PATHS.convertToPng, icoSrc, `${getTmpDirPath()}/icon.png`);
} }
function convertToIco(icoSrc) { function convertToIco(icoSrc) {
return iconShellHelper(SCRIPT_PATHS.convertToIco, icoSrc, `${getTmpDirPath()}/icon.ico`); return iconShellHelper(SCRIPT_PATHS.convertToIco, icoSrc, `${getTmpDirPath()}/icon.ico`);
} }
function convertToIcns(icoSrc) { function convertToIcns(icoSrc) {
if (!isOSX()) { if (!isOSX()) {
return new Promise((resolve, reject) => reject('OSX is required to convert to a .icns icon')); return new Promise((resolve, reject) => reject('OSX is required to convert to a .icns icon'));
} }
return iconShellHelper(SCRIPT_PATHS.convertToIcns, icoSrc, `${getTmpDirPath()}/icon.icns`); return iconShellHelper(SCRIPT_PATHS.convertToIcns, icoSrc, `${getTmpDirPath()}/icon.icns`);
} }
export default { export default {
singleIco, singleIco,
convertToPng, convertToPng,
convertToIco, convertToIco,
convertToIcns convertToIcns,
}; };

View File

@ -1,27 +1,29 @@
// TODO: remove this file and use quiet mode of new version of electron packager
class PackagerConsole { class PackagerConsole {
constructor() { constructor() {
this.logs = []; this.logs = [];
} }
_log(...messages) { _log(...messages) {
this.logs.push(...messages); this.logs.push(...messages);
} }
override() { override() {
this.consoleError = console.error; this.consoleError = console.error;
// need to bind because somehow when _log() is called this refers to console // need to bind because somehow when _log() is called this refers to console
console.error = this._log.bind(this); // eslint-disable-next-line no-underscore-dangle
} console.error = this._log.bind(this);
}
restore() { restore() {
console.error = this.consoleError; console.error = this.consoleError;
} }
playback() { playback() {
console.log(this.logs.join(' ')); console.log(this.logs.join(' '));
} }
} }
export default PackagerConsole; export default PackagerConsole;

View File

@ -5,117 +5,100 @@ import tmp from 'tmp';
import gitCloud from 'gitcloud'; import gitCloud from 'gitcloud';
import helpers from './../helpers/helpers'; import helpers from './../helpers/helpers';
const {downloadFile, allowedIconFormats} = helpers; const { downloadFile, allowedIconFormats } = helpers;
tmp.setGracefulCleanup(); tmp.setGracefulCleanup();
const GITCLOUD_SPACE_DELIMITER = '-'; 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) { function getMaxMatchScore(iconWithScores) {
return iconWithScores.reduce((maxScore, currentIcon) => { return iconWithScores.reduce((maxScore, currentIcon) => {
const currentScore = currentIcon.score; const currentScore = currentIcon.score;
if (currentScore > maxScore) { if (currentScore > maxScore) {
return currentScore; return currentScore;
} }
return maxScore; return maxScore;
}, 0); }, 0);
} }
/** /**
* also maps ext to icon object * also maps ext to icon object
*/ */
function getMatchingIcons(iconWithScores, maxScore) { function getMatchingIcons(iconsWithScores, maxScore) {
return iconWithScores return iconsWithScores
.filter(item => { .filter(item => item.score === maxScore)
return item.score === maxScore; .map(item => Object.assign({}, item, { ext: path.extname(item.url) }));
})
.map(item => {
return Object.assign(
{},
item,
{ext: path.extname(item.url)}
);
});
} }
function writeFilePromise(outPath, data) { function mapIconWithMatchScore(fileIndex, targetUrl) {
return new Promise((resolve, reject) => { const normalisedTargetUrl = targetUrl.toLowerCase();
fs.writeFile(outPath, data, error => { return fileIndex
if (error) { .map((item) => {
reject(error); const itemWords = item.name.split(GITCLOUD_SPACE_DELIMITER);
return; const score = itemWords.reduce((currentScore, word) => {
} if (normalisedTargetUrl.includes(word)) {
resolve(outPath); return currentScore + 1;
}); }
return currentScore;
}, 0);
return Object.assign({}, item, { score });
}); });
} }
function inferFromPage(targetUrl, platform, outDir) { function inferIconFromStore(targetUrl, platform) {
let preferredExt = '.png'; const allowedFormats = new Set(allowedIconFormats(platform));
if (platform === 'win32') {
preferredExt = '.ico';
}
// todo might want to pass list of preferences instead return gitCloud('http://jiahaog.com/nativefier-icons/')
return pageIcon(targetUrl, {ext: preferredExt}) .then((fileIndex) => {
.then(icon => { const iconWithScores = mapIconWithMatchScore(fileIndex, targetUrl);
if (!icon) { const maxScore = getMaxMatchScore(iconWithScores);
return null;
}
const outfilePath = path.join(outDir, `/icon${icon.ext}`); if (maxScore === 0) {
return writeFilePromise(outfilePath, icon.data); 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 * @param {string} targetUrl
@ -123,16 +106,15 @@ function inferFromPage(targetUrl, platform, outDir) {
* @param {string} outDir * @param {string} outDir
*/ */
function inferIconFromUrlToPath(targetUrl, platform, outDir) { function inferIconFromUrlToPath(targetUrl, platform, outDir) {
return inferIconFromStore(targetUrl, platform)
.then((icon) => {
if (!icon) {
return inferFromPage(targetUrl, platform, outDir);
}
return inferIconFromStore(targetUrl, platform) const outfilePath = path.join(outDir, `/icon${icon.ext}`);
.then(icon => { return writeFilePromise(outfilePath, icon.data);
if (!icon) { });
return inferFromPage(targetUrl, platform, outDir);
}
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 * @param {string} platform
*/ */
function inferIcon(targetUrl, platform) { function inferIcon(targetUrl, platform) {
const tmpObj = tmp.dirSync({unsafeCleanup: true}); const tmpObj = tmp.dirSync({ unsafeCleanup: true });
const tmpPath = tmpObj.name; const tmpPath = tmpObj.name;
return inferIconFromUrlToPath(targetUrl, platform, tmpPath); return inferIconFromUrlToPath(targetUrl, platform, tmpPath);
} }
export default inferIcon; export default inferIcon;

View File

@ -1,23 +1,23 @@
import os from 'os'; import os from 'os';
function inferPlatform() { function inferPlatform() {
const platform = os.platform(); const platform = os.platform();
if (platform === 'darwin' || platform === 'win32' || platform === 'linux') { if (platform === 'darwin' || platform === 'win32' || platform === 'linux') {
return platform; return platform;
} }
throw `Untested platform ${platform} detected`; throw new Error(`Untested platform ${platform} detected`);
} }
function inferArch() { function inferArch() {
const arch = os.arch(); const arch = os.arch();
if (arch !== 'ia32' && arch !== 'x64') { if (arch !== 'ia32' && arch !== 'x64') {
throw `Incompatible architecture ${arch} detected`; throw new Error(`Incompatible architecture ${arch} detected`);
} }
return arch; return arch;
} }
export default { export default {
inferPlatform: inferPlatform, inferPlatform,
inferArch: inferArch inferArch,
}; };

View File

@ -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'; 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) { function inferTitle(url) {
const options = { const options = {
method: 'get', method: 'get',
url, url,
headers: { headers: {
// fake a user agent because pages like http://messenger.com will throw 404 error // fake a user agent because pages like http://messenger.com will throw 404 error
'User-Agent': USER_AGENT 'User-Agent': USER_AGENT,
} },
}; };
return axios(options).then(({data}) => { return axios(options).then(({ data }) => {
const $ = cheerio.load(data); const $ = cheerio.load(data);
return $('title').first().text().replace(/\//g, ''); return $('title').first().text().replace(/\//g, '');
}); });
} }
export default inferTitle; export default inferTitle;

View File

@ -6,50 +6,49 @@ const ELECTRON_VERSIONS_URL = 'https://atom.io/download/atom-shell/index.json';
const DEFAULT_CHROME_VERSION = '56.0.2924.87'; const DEFAULT_CHROME_VERSION = '56.0.2924.87';
function getChromeVersionForElectronVersion(electronVersion, url = ELECTRON_VERSIONS_URL) { function getChromeVersionForElectronVersion(electronVersion, url = ELECTRON_VERSIONS_URL) {
return axios.get(url, {timeout: 5000}) return axios.get(url, { timeout: 5000 })
.then(response => { .then((response) => {
if (response.status !== 200) { if (response.status !== 200) {
throw `Bad request: Status code ${response.status}`; throw new Error(`Bad request: Status code ${response.status}`);
} }
const data = response.data; const data = response.data;
const electronVersionToChromeVersion = _.zipObject(data.map(d => d.version), data.map(d => d.chrome)); const electronVersionToChromeVersion = _.zipObject(data.map(d => d.version),
data.map(d => d.chrome));
if (!(electronVersion in electronVersionToChromeVersion)) { if (!(electronVersion in electronVersionToChromeVersion)) {
throw `Electron version '${electronVersion}' not found in retrieved version list!`; throw new Error(`Electron version '${electronVersion}' not found in retrieved version list!`);
} }
return electronVersionToChromeVersion[electronVersion]; return electronVersionToChromeVersion[electronVersion];
}); });
} }
export function getUserAgentString(chromeVersion, platform) { export function getUserAgentString(chromeVersion, platform) {
let userAgent; let userAgent;
switch (platform) { switch (platform) {
case 'darwin': 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`; userAgent = `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`;
break; break;
case 'win32': case 'win32':
userAgent = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`; userAgent = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`;
break; break;
case 'linux': case 'linux':
userAgent = `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`; userAgent = `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`;
break; break;
default: default:
throw 'Error invalid platform specified to getUserAgentString()'; throw new Error('Error invalid platform specified to getUserAgentString()');
} }
return userAgent; return userAgent;
} }
function inferUserAgent(electronVersion, platform, url = ELECTRON_VERSIONS_URL) { function inferUserAgent(electronVersion, platform, url = ELECTRON_VERSIONS_URL) {
return getChromeVersionForElectronVersion(electronVersion, url) return getChromeVersionForElectronVersion(electronVersion, url)
.then(chromeVersion => { .then(chromeVersion => getUserAgentString(chromeVersion, platform))
return getUserAgentString(chromeVersion, platform); .catch(() => {
}) log.warn(`Unable to infer chrome version for user agent, using ${DEFAULT_CHROME_VERSION}`);
.catch(() => { return getUserAgentString(DEFAULT_CHROME_VERSION, platform);
log.warn(`Unable to infer chrome version for user agent, using ${DEFAULT_CHROME_VERSION}`); });
return getUserAgentString(DEFAULT_CHROME_VERSION, platform);
});
} }
export default inferUserAgent; export default inferUserAgent;

View File

@ -2,25 +2,25 @@ import url from 'url';
import validator from 'validator'; import validator from 'validator';
function appendProtocol(testUrl) { function appendProtocol(testUrl) {
const parsed = url.parse(testUrl); const parsed = url.parse(testUrl);
if (!parsed.protocol) { if (!parsed.protocol) {
return `http://${testUrl}`; return `http://${testUrl}`;
} }
return testUrl; return testUrl;
} }
function normalizeUrl(testUrl) { function normalizeUrl(testUrl) {
const urlWithProtocol = appendProtocol(testUrl); const urlWithProtocol = appendProtocol(testUrl);
const validatorOptions = { const validatorOptions = {
require_protocol: true, require_protocol: true,
require_tld: false, require_tld: false,
allow_trailing_dot: true // mDNS addresses, https://github.com/jiahaog/nativefier/issues/308 allow_trailing_dot: true, // mDNS addresses, https://github.com/jiahaog/nativefier/issues/308
}; };
if (!validator.isURL(urlWithProtocol, validatorOptions)) { if (!validator.isURL(urlWithProtocol, validatorOptions)) {
throw `Your Url: "${urlWithProtocol}" is invalid!`; throw new Error(`Your Url: "${urlWithProtocol}" is invalid!`);
} }
return urlWithProtocol; return urlWithProtocol;
} }
export default normalizeUrl; export default normalizeUrl;

View File

@ -11,13 +11,31 @@ import inferUserAgent from './../infer/inferUserAgent';
import normalizeUrl from './normalizeUrl'; import normalizeUrl from './normalizeUrl';
import packageJson from './../../package.json'; import packageJson from './../../package.json';
const {inferPlatform, inferArch} = inferOs; const { inferPlatform, inferArch } = inferOs;
const PLACEHOLDER_APP_DIR = path.join(__dirname, '../../', 'app'); const PLACEHOLDER_APP_DIR = path.join(__dirname, '../../', 'app');
const ELECTRON_VERSION = '1.6.6'; const ELECTRON_VERSION = '1.6.6';
const DEFAULT_APP_NAME = 'APP'; 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 * @callback optionsCallback
* @param error * @param error
@ -30,143 +48,128 @@ const DEFAULT_APP_NAME = 'APP';
* @param {optionsCallback} callback * @param {optionsCallback} callback
*/ */
function optionsFactory(inpOptions, callback) { function optionsFactory(inpOptions, callback) {
const options = {
const options = { dir: PLACEHOLDER_APP_DIR,
dir: PLACEHOLDER_APP_DIR, name: inpOptions.name,
name: inpOptions.name, targetUrl: normalizeUrl(inpOptions.targetUrl),
targetUrl: normalizeUrl(inpOptions.targetUrl), platform: inpOptions.platform || inferPlatform(),
platform: inpOptions.platform || inferPlatform(), arch: inpOptions.arch || inferArch(),
arch: inpOptions.arch || inferArch(), electronVersion: inpOptions.electronVersion || ELECTRON_VERSION,
electronVersion: inpOptions.electronVersion || ELECTRON_VERSION, nativefierVersion: packageJson.version,
nativefierVersion: packageJson.version, out: inpOptions.out || process.cwd(),
out: inpOptions.out || process.cwd(), overwrite: inpOptions.overwrite,
overwrite: inpOptions.overwrite, asar: inpOptions.conceal || false,
asar: inpOptions.conceal || false, icon: inpOptions.icon,
icon: inpOptions.icon, counter: inpOptions.counter || false,
counter: inpOptions.counter || false, width: inpOptions.width || 1280,
width: inpOptions.width || 1280, height: inpOptions.height || 800,
height: inpOptions.height || 800, minWidth: inpOptions.minWidth,
minWidth: inpOptions.minWidth, minHeight: inpOptions.minHeight,
minHeight: inpOptions.minHeight, maxWidth: inpOptions.maxWidth,
maxWidth: inpOptions.maxWidth, maxHeight: inpOptions.maxHeight,
maxHeight: inpOptions.maxHeight, showMenuBar: inpOptions.showMenuBar || false,
showMenuBar: inpOptions.showMenuBar || false, fastQuit: inpOptions.fastQuit || false,
fastQuit: inpOptions.fastQuit || false, userAgent: inpOptions.userAgent,
userAgent: inpOptions.userAgent, ignoreCertificate: inpOptions.ignoreCertificate || false,
ignoreCertificate: inpOptions.ignoreCertificate || false, insecure: inpOptions.insecure || false,
insecure: inpOptions.insecure || false, flashPluginDir: inpOptions.flashPath || inpOptions.flash || null,
flashPluginDir: inpOptions.flashPath || inpOptions.flash || null, inject: inpOptions.inject || null,
inject: inpOptions.inject || null, ignore: 'src',
ignore: 'src', fullScreen: inpOptions.fullScreen || false,
fullScreen: inpOptions.fullScreen || false, maximize: inpOptions.maximize || false,
maximize: inpOptions.maximize || false, hideWindowFrame: inpOptions.hideWindowFrame,
hideWindowFrame: inpOptions.hideWindowFrame, verbose: inpOptions.verbose,
verbose: inpOptions.verbose, disableContextMenu: inpOptions.disableContextMenu,
disableContextMenu: inpOptions.disableContextMenu, disableDevTools: inpOptions.disableDevTools,
disableDevTools: inpOptions.disableDevTools, crashReporter: inpOptions.crashReporter,
crashReporter: inpOptions.crashReporter,
// workaround for electron-packager#375 // workaround for electron-packager#375
tmpdir: false, tmpdir: false,
zoom: inpOptions.zoom || 1.0, zoom: inpOptions.zoom || 1.0,
internalUrls: inpOptions.internalUrls || null, internalUrls: inpOptions.internalUrls || null,
singleInstance: inpOptions.singleInstance || false singleInstance: inpOptions.singleInstance || false,
}; };
if (options.verbose) { if (options.verbose) {
log.setLevel('trace'); log.setLevel('trace');
} else { } else {
log.setLevel('error'); 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;
} }
callback(null, sanitizeOptions(options));
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;
} }
export default optionsFactory; export default optionsFactory;

View File

@ -0,0 +1,2 @@
env:
mocha: true

View File

@ -7,36 +7,36 @@ import os from 'os';
import path from 'path'; import path from 'path';
import convertToIcns from './../../lib/helpers/convertToIcns'; 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 // Prerequisite for test: to use OSX with sips, iconutil and imagemagick convert
function testConvertPng(pngName, done) { function testConvertPng(pngName, done) {
convertToIcns(path.join(__dirname, '../../', 'test-resources', pngName), (error, icnsPath) => { convertToIcns(path.join(__dirname, '../../', 'test-resources', pngName), (error, icnsPath) => {
if (error) { if (error) {
done(error); done(error);
return; return;
} }
let stat = fs.statSync(icnsPath); const stat = fs.statSync(icnsPath);
assert.isTrue(stat.isFile(), 'Output icns file should be a path'); assert.isTrue(stat.isFile(), 'Output icns file should be a path');
done(); done();
}); });
} }
describe('Get Icon Module', function() { describe('Get Icon Module', () => {
it('Can convert icons', function() { it('Can convert icons', () => {
if (os.platform() !== 'darwin') { if (os.platform() !== 'darwin') {
console.warn('Skipping png conversion tests, OSX is required'); console.warn('Skipping png conversion tests, OSX is required');
return; return;
} }
it('Can convert a rgb png to icns', function(done) { it('Can convert a rgb png to icns', (done) => {
testConvertPng('iconSample.png', done); testConvertPng('iconSample.png', done);
});
it('Can convert a grey png to icns', function(done) {
testConvertPng('iconSampleGrey.png', done);
});
}); });
it('Can convert a grey png to icns', (done) => {
testConvertPng('iconSampleGrey.png', done);
});
});
}); });

View File

@ -11,65 +11,65 @@ tmp.setGracefulCleanup();
const assert = chai.assert; const assert = chai.assert;
function checkApp(appPath, inputOptions, callback) { function checkApp(appPath, inputOptions, callback) {
try { try {
let relPathToConfig; let relPathToConfig;
switch (inputOptions.platform) { switch (inputOptions.platform) {
case 'darwin': case 'darwin':
relPathToConfig = path.join('google-test-app.app', 'Contents/Resources/app'); relPathToConfig = path.join('google-test-app.app', 'Contents/Resources/app');
break; break;
case 'linux': case 'linux':
relPathToConfig = 'resources/app'; relPathToConfig = 'resources/app';
break; break;
case 'win32': case 'win32':
relPathToConfig = 'resources/app'; relPathToConfig = 'resources/app';
break; break;
default: default:
throw 'Unknown app platform'; 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);
} }
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() { describe('Nativefier Module', function () {
this.timeout(240000); this.timeout(240000);
it('Can build an app from a target url', function(done) { it('Can build an app from a target url', (done) => {
async.eachSeries(PLATFORMS, (platform, callback) => { 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; options.platform = platform;
const options = { nativefier(options, (error, appPath) => {
name: 'google-test-app', if (error) {
targetUrl: 'http://google.com', callback(error);
out: tmpPath, return;
overwrite: true, }
platform: null
};
options.platform = platform; checkApp(appPath, options, (error) => {
nativefier(options, (error, appPath) => { callback(error);
if (error) {
callback(error);
return;
}
checkApp(appPath, options, error => {
callback(error);
});
});
}, error => {
done(error);
}); });
});
}, (error) => {
done(error);
}); });
});
}); });

View File

@ -1,49 +1,47 @@
import inferUserAgent from './../../lib/infer/inferUserAgent';
import chai from 'chai'; import chai from 'chai';
import _ from 'lodash'; import _ from 'lodash';
import inferUserAgent from './../../lib/infer/inferUserAgent';
const assert = chai.assert; const assert = chai.assert;
const TEST_RESULT = { 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', 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', 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' 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) { function testPlatform(platform) {
return inferUserAgent('0.37.1', platform) return inferUserAgent('0.37.1', platform)
.then(userAgent => { .then((userAgent) => {
assert.equal(userAgent, TEST_RESULT[platform], 'Correct user agent should be inferred'); assert.equal(userAgent, TEST_RESULT[platform], 'Correct user agent should be inferred');
}); });
} }
describe('Infer User Agent', function() { describe('Infer User Agent', function () {
this.timeout(15000); this.timeout(15000);
it('Can infer userAgent for all platforms', function(done) { it('Can infer userAgent for all platforms', (done) => {
const testPromises = _.keys(TEST_RESULT).map(platform => { const testPromises = _.keys(TEST_RESULT).map(platform => testPlatform(platform));
return testPlatform(platform); Promise
});
Promise
.all(testPromises) .all(testPromises)
.then(() => { .then(() => {
done(); done();
}) })
.catch(error => { .catch((error) => {
done(error); done(error);
}); });
}); });
it('Connection error will still get a user agent', function(done) { it('Connection error will still get a user agent', (done) => {
const TIMEOUT_URL = 'http://www.google.com:81/'; const TIMEOUT_URL = 'http://www.google.com:81/';
inferUserAgent('1.6.7', 'darwin', TIMEOUT_URL) inferUserAgent('1.6.7', 'darwin', TIMEOUT_URL)
.then(userAgent => { .then((userAgent) => {
assert.equal( assert.equal(
userAgent, 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', '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); .catch(done);
}); });
}); });

View File

@ -1,25 +1,25 @@
import normalizeUrl from '../../../src/options/normalizeUrl';
import chai from 'chai'; import chai from 'chai';
import normalizeUrl from '../../../src/options/normalizeUrl';
const assert = chai.assert; const assert = chai.assert;
const expect = chai.expect; const expect = chai.expect;
describe('Normalize URL', () => { describe('Normalize URL', () => {
describe('given a valid URL without a protocol', () => {
describe('given a valid URL without a protocol', () => { it('should allow the url', () => {
it('should allow the url', () => { assert.equal(normalizeUrl('http://www.google.com'), 'http://www.google.com');
assert.equal(normalizeUrl('http://www.google.com'), 'http://www.google.com');
});
}); });
});
describe('given a valid URL without a protocol', () => { describe('given a valid URL without a protocol', () => {
it('should allow the url and prepend the HTTP protocol', () => { it('should allow the url and prepend the HTTP protocol', () => {
assert.equal(normalizeUrl('www.google.com'), 'http://www.google.com'); assert.equal(normalizeUrl('www.google.com'), 'http://www.google.com');
});
}); });
});
describe('given an invalid URL', () => { describe('given an invalid URL', () => {
it('should throw an exception', () => { it('should throw an exception', () => {
expect(() => normalizeUrl('http://ssddfoo bar')).to.throw('Your Url: "http://ssddfoo bar" is invalid!'); expect(() => normalizeUrl('http://ssddfoo bar')).to.throw('Your Url: "http://ssddfoo bar" is invalid!');
});
}); });
});
}); });

View File

@ -1,24 +1,24 @@
var electronPublicApi = ['electron']; const electronPublicApi = ['electron'];
var nodeModules = {}; const nodeModules = {};
electronPublicApi.forEach(apiString => { electronPublicApi.forEach((apiString) => {
nodeModules[apiString] = 'commonjs ' + apiString; nodeModules[apiString] = `commonjs ${apiString}`;
}); });
module.exports = { module.exports = {
target: 'node', target: 'node',
output: { output: {
filename: 'main.js' filename: 'main.js',
}, },
node: { node: {
global: false, global: false,
__dirname: false __dirname: false,
}, },
externals: nodeModules, externals: nodeModules,
module: { module: {
loaders: [ loaders: [
{test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader'} { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' },
] ],
}, },
devtool: 'source-map' devtool: 'source-map',
}; };