mirror of
https://github.com/Llewellynvdm/nativefier.git
synced 2024-12-22 18:18:55 +00:00
Update eslint and use Airbnb style
- Add `npm run lint:fix` command - Cleanup inferIcon.js logic slightly
This commit is contained in:
parent
461c7a38f0
commit
8f78dd03af
49
.eslintrc.js
49
.eslintrc.js
@ -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
8
.eslintrc.yml
Normal 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']}]
|
20
.github/CONTRIBUTING.md
vendored
20
.github/CONTRIBUTING.md
vendored
@ -15,23 +15,19 @@ Please include the following in your new issue:
|
||||
|
||||
See [here](https://github.com/jiahaog/nativefier#development) for instructions on how to set up a development environment.
|
||||
|
||||
Follow the current code style, and make sure tests and lints pass before submitting with the following commands:
|
||||
We follow the [Airbnb Style Guide](https://github.com/airbnb/javascript), please make sure tests and lints pass when you submit your pull request.
|
||||
|
||||
Run the following command before submitting the pull request:
|
||||
The following commands might be helpful:
|
||||
|
||||
```bash
|
||||
# Run tests and linting
|
||||
$ npm run ci
|
||||
```
|
||||
# Run specs and lint
|
||||
npm run ci
|
||||
|
||||
Or you can run them separately:
|
||||
# Run specs only
|
||||
npm run test
|
||||
|
||||
```bash
|
||||
# Tests
|
||||
$ npm run test
|
||||
|
||||
# Lint source files
|
||||
$ npm run lint
|
||||
# Run linter only
|
||||
npm run lint
|
||||
```
|
||||
|
||||
Thank you so much for your contribution!
|
||||
|
2
app/.eslintrc.yml
Normal file
2
app/.eslintrc.yml
Normal file
@ -0,0 +1,2 @@
|
||||
settings:
|
||||
import/core-modules: [ electron ]
|
@ -1,3 +1,5 @@
|
||||
// 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) {
|
||||
@ -8,10 +10,9 @@ function initContextMenu(mainWindow) {
|
||||
click: () => {
|
||||
if (targetHref) {
|
||||
shell.openExternal(targetHref);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Open in new window',
|
||||
click: () => {
|
||||
@ -22,7 +23,7 @@ function initContextMenu(mainWindow) {
|
||||
|
||||
mainWindow.useDefaultWindowBehaviour = true;
|
||||
mainWindow.webContents.send('contextMenuClosed');
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Copy link location',
|
||||
@ -34,8 +35,8 @@ function initContextMenu(mainWindow) {
|
||||
|
||||
mainWindow.useDefaultWindowBehaviour = true;
|
||||
mainWindow.webContents.send('contextMenuClosed');
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const contextMenu = Menu.buildFromTemplate(contextMenuTemplate);
|
||||
|
@ -2,15 +2,15 @@ import {BrowserWindow, ipcMain} from 'electron';
|
||||
import path from 'path';
|
||||
|
||||
function createLoginWindow(loginCallback) {
|
||||
var loginWindow = new BrowserWindow({
|
||||
const loginWindow = new BrowserWindow({
|
||||
width: 300,
|
||||
height: 400,
|
||||
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]);
|
||||
loginWindow.close();
|
||||
});
|
||||
|
@ -10,17 +10,52 @@ const {isOSX, linkIsInternal, getCssToInject, shouldInjectCss} = helpers;
|
||||
|
||||
const ZOOM_INTERVAL = 0.1;
|
||||
|
||||
function maybeHideWindow(window, event, fastQuit) {
|
||||
if (isOSX() && !fastQuit) {
|
||||
// this is called when exiting from clicking the cross button on the window
|
||||
event.preventDefault();
|
||||
window.hide();
|
||||
}
|
||||
// will close the window on other platforms
|
||||
}
|
||||
|
||||
function maybeInjectCss(browserWindow) {
|
||||
if (!shouldInjectCss()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cssToInject = getCssToInject();
|
||||
|
||||
const injectCss = () => {
|
||||
browserWindow.webContents.insertCSS(cssToInject);
|
||||
};
|
||||
|
||||
browserWindow.webContents.on('did-finish-load', () => {
|
||||
// remove the injection of css the moment the page is loaded
|
||||
browserWindow.webContents.removeListener('did-get-response-details', injectCss);
|
||||
});
|
||||
|
||||
// on every page navigation inject the css
|
||||
browserWindow.webContents.on('did-navigate', () => {
|
||||
// we have to inject the css in did-get-response-details to prevent the fouc
|
||||
// will run multiple times
|
||||
browserWindow.webContents.on('did-get-response-details', injectCss);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {{}} options AppArgs from nativefier.json
|
||||
* @param {{}} inpOptions AppArgs from nativefier.json
|
||||
* @param {function} onAppQuit
|
||||
* @param {function} setDockBadge
|
||||
* @returns {electron.BrowserWindow}
|
||||
*/
|
||||
function createMainWindow(options, onAppQuit, setDockBadge) {
|
||||
function createMainWindow(inpOptions, onAppQuit, setDockBadge) {
|
||||
const options = Object.assign({}, inpOptions);
|
||||
const mainWindowState = windowStateKeeper({
|
||||
defaultWidth: options.width || 1280,
|
||||
defaultHeight: options.height || 800
|
||||
defaultHeight: options.height || 800,
|
||||
});
|
||||
|
||||
const mainWindow = new BrowserWindow({
|
||||
@ -43,12 +78,12 @@ function createMainWindow(options, onAppQuit, setDockBadge) {
|
||||
nodeIntegration: false,
|
||||
webSecurity: !options.insecure,
|
||||
preload: path.join(__dirname, 'static', 'preload.js'),
|
||||
zoomFactor: options.zoom
|
||||
zoomFactor: options.zoom,
|
||||
},
|
||||
// after webpack path here should reference `resources/app/`
|
||||
icon: path.join(__dirname, '../', '/icon.png'),
|
||||
// set to undefined and not false because explicitly setting to false will disable full screen
|
||||
fullscreen: options.fullScreen || undefined
|
||||
fullscreen: options.fullScreen || undefined,
|
||||
});
|
||||
|
||||
mainWindowState.manage(mainWindow);
|
||||
@ -88,16 +123,17 @@ function createMainWindow(options, onAppQuit, setDockBadge) {
|
||||
buttons: ['Yes', 'Cancel'],
|
||||
defaultId: 1,
|
||||
title: 'Clear cache confirmation',
|
||||
message: 'This will clear all data (cookies, local storage etc) from this app. Are you sure you wish to proceed?'
|
||||
}, response => {
|
||||
if (response === 0) {
|
||||
message: 'This will clear all data (cookies, local storage etc) from this app. Are you sure you wish to proceed?',
|
||||
}, (response) => {
|
||||
if (response !== 0) {
|
||||
return;
|
||||
}
|
||||
const session = mainWindow.webContents.session;
|
||||
session.clearStorageData(() => {
|
||||
session.clearCache(() => {
|
||||
mainWindow.loadURL(options.targetUrl);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@ -109,9 +145,7 @@ function createMainWindow(options, onAppQuit, setDockBadge) {
|
||||
mainWindow.webContents.goForward();
|
||||
};
|
||||
|
||||
const getCurrentUrl = () => {
|
||||
return mainWindow.webContents.getURL();
|
||||
};
|
||||
const getCurrentUrl = () => mainWindow.webContents.getURL();
|
||||
|
||||
const menuOptions = {
|
||||
nativefierVersion: options.nativefierVersion,
|
||||
@ -122,9 +156,9 @@ function createMainWindow(options, onAppQuit, setDockBadge) {
|
||||
zoomBuildTimeValue: options.zoom,
|
||||
goBack: onGoBack,
|
||||
goForward: onGoForward,
|
||||
getCurrentUrl: getCurrentUrl,
|
||||
clearAppData: clearAppData,
|
||||
disableDevTools: options.disableDevTools
|
||||
getCurrentUrl,
|
||||
clearAppData,
|
||||
disableDevTools: options.disableDevTools,
|
||||
};
|
||||
|
||||
createMenu(menuOptions);
|
||||
@ -143,7 +177,7 @@ function createMainWindow(options, onAppQuit, setDockBadge) {
|
||||
|
||||
if (options.counter) {
|
||||
mainWindow.on('page-title-updated', (e, title) => {
|
||||
const itemCountRegex = /[\(\[{](\d*?)[}\]\)]/;
|
||||
const itemCountRegex = /[([{](\d*?)[}\])]/;
|
||||
const match = itemCountRegex.exec(title);
|
||||
if (match) {
|
||||
setDockBadge(match[1]);
|
||||
@ -178,7 +212,7 @@ function createMainWindow(options, onAppQuit, setDockBadge) {
|
||||
|
||||
mainWindow.loadURL(options.targetUrl);
|
||||
|
||||
mainWindow.on('close', event => {
|
||||
mainWindow.on('close', (event) => {
|
||||
if (mainWindow.isFullScreen()) {
|
||||
mainWindow.setFullScreen(false);
|
||||
mainWindow.once('leave-full-screen', maybeHideWindow.bind(this, mainWindow, event, options.fastQuit));
|
||||
@ -191,42 +225,10 @@ function createMainWindow(options, onAppQuit, setDockBadge) {
|
||||
|
||||
ipcMain.on('cancelNewWindowOverride', () => {
|
||||
const allWindows = BrowserWindow.getAllWindows();
|
||||
allWindows.forEach(window => {
|
||||
allWindows.forEach((window) => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
window.useDefaultWindowBehaviour = false;
|
||||
});
|
||||
});
|
||||
|
||||
function maybeHideWindow(window, event, fastQuit) {
|
||||
if (isOSX() && !fastQuit) {
|
||||
// this is called when exiting from clicking the cross button on the window
|
||||
event.preventDefault();
|
||||
window.hide();
|
||||
}
|
||||
// will close the window on other platforms
|
||||
}
|
||||
|
||||
function maybeInjectCss(browserWindow) {
|
||||
if (!shouldInjectCss()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cssToInject = getCssToInject();
|
||||
|
||||
const injectCss = () => {
|
||||
browserWindow.webContents.insertCSS(cssToInject);
|
||||
};
|
||||
|
||||
browserWindow.webContents.on('did-finish-load', () => {
|
||||
// remove the injection of css the moment the page is loaded
|
||||
browserWindow.webContents.removeListener('did-get-response-details', injectCss);
|
||||
});
|
||||
|
||||
// on every page navigation inject the css
|
||||
browserWindow.webContents.on('did-navigate', () => {
|
||||
// we have to inject the css in did-get-response-details to prevent the fouc
|
||||
// will run multiple times
|
||||
browserWindow.webContents.on('did-get-response-details', injectCss);
|
||||
});
|
||||
}
|
||||
|
||||
export default createMainWindow;
|
||||
|
@ -13,7 +13,17 @@ import {Menu, shell, clipboard} from 'electron';
|
||||
* @param clearAppData
|
||||
* @param disableDevTools
|
||||
*/
|
||||
function createMenu({nativefierVersion, appQuit, zoomIn, zoomOut, zoomReset, zoomBuildTimeValue, goBack, goForward, getCurrentUrl, clearAppData, disableDevTools}) {
|
||||
function createMenu({ nativefierVersion,
|
||||
appQuit,
|
||||
zoomIn,
|
||||
zoomOut,
|
||||
zoomReset,
|
||||
zoomBuildTimeValue,
|
||||
goBack,
|
||||
goForward,
|
||||
getCurrentUrl,
|
||||
clearAppData,
|
||||
disableDevTools }) {
|
||||
if (Menu.getApplicationMenu()) {
|
||||
return;
|
||||
}
|
||||
@ -28,25 +38,25 @@ function createMenu({nativefierVersion, appQuit, zoomIn, zoomOut, zoomReset, zoo
|
||||
{
|
||||
label: 'Undo',
|
||||
accelerator: 'CmdOrCtrl+Z',
|
||||
role: 'undo'
|
||||
role: 'undo',
|
||||
},
|
||||
{
|
||||
label: 'Redo',
|
||||
accelerator: 'Shift+CmdOrCtrl+Z',
|
||||
role: 'redo'
|
||||
role: 'redo',
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
type: 'separator',
|
||||
},
|
||||
{
|
||||
label: 'Cut',
|
||||
accelerator: 'CmdOrCtrl+X',
|
||||
role: 'cut'
|
||||
role: 'cut',
|
||||
},
|
||||
{
|
||||
label: 'Copy',
|
||||
accelerator: 'CmdOrCtrl+C',
|
||||
role: 'copy'
|
||||
role: 'copy',
|
||||
},
|
||||
{
|
||||
label: 'Copy Current URL',
|
||||
@ -54,25 +64,25 @@ function createMenu({nativefierVersion, appQuit, zoomIn, zoomOut, zoomReset, zoo
|
||||
click: () => {
|
||||
const currentURL = getCurrentUrl();
|
||||
clipboard.writeText(currentURL);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Paste',
|
||||
accelerator: 'CmdOrCtrl+V',
|
||||
role: 'paste'
|
||||
role: 'paste',
|
||||
},
|
||||
{
|
||||
label: 'Select All',
|
||||
accelerator: 'CmdOrCtrl+A',
|
||||
role: 'selectall'
|
||||
role: 'selectall',
|
||||
},
|
||||
{
|
||||
label: 'Clear App Data',
|
||||
click: () => {
|
||||
clearAppData();
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'View',
|
||||
@ -82,14 +92,14 @@ function createMenu({nativefierVersion, appQuit, zoomIn, zoomOut, zoomReset, zoo
|
||||
accelerator: 'CmdOrCtrl+[',
|
||||
click: () => {
|
||||
goBack();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Forward',
|
||||
accelerator: 'CmdOrCtrl+]',
|
||||
click: () => {
|
||||
goForward();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Reload',
|
||||
@ -98,10 +108,10 @@ function createMenu({nativefierVersion, appQuit, zoomIn, zoomOut, zoomReset, zoo
|
||||
if (focusedWindow) {
|
||||
focusedWindow.reload();
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
type: 'separator',
|
||||
},
|
||||
{
|
||||
label: 'Toggle Full Screen',
|
||||
@ -115,7 +125,7 @@ function createMenu({nativefierVersion, appQuit, zoomIn, zoomOut, zoomReset, zoo
|
||||
if (focusedWindow) {
|
||||
focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Zoom In',
|
||||
@ -127,7 +137,7 @@ function createMenu({nativefierVersion, appQuit, zoomIn, zoomOut, zoomReset, zoo
|
||||
})(),
|
||||
click: () => {
|
||||
zoomIn();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Zoom Out',
|
||||
@ -139,7 +149,7 @@ function createMenu({nativefierVersion, appQuit, zoomIn, zoomOut, zoomReset, zoo
|
||||
})(),
|
||||
click: () => {
|
||||
zoomOut();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: zoomResetLabel,
|
||||
@ -151,7 +161,7 @@ function createMenu({nativefierVersion, appQuit, zoomIn, zoomOut, zoomReset, zoo
|
||||
})(),
|
||||
click: () => {
|
||||
zoomReset();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Toggle Developer Tools',
|
||||
@ -165,9 +175,9 @@ function createMenu({nativefierVersion, appQuit, zoomIn, zoomOut, zoomReset, zoo
|
||||
if (focusedWindow) {
|
||||
focusedWindow.toggleDevTools();
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Window',
|
||||
@ -176,14 +186,14 @@ function createMenu({nativefierVersion, appQuit, zoomIn, zoomOut, zoomReset, zoo
|
||||
{
|
||||
label: 'Minimize',
|
||||
accelerator: 'CmdOrCtrl+M',
|
||||
role: 'minimize'
|
||||
role: 'minimize',
|
||||
},
|
||||
{
|
||||
label: 'Close',
|
||||
accelerator: 'CmdOrCtrl+W',
|
||||
role: 'close'
|
||||
}
|
||||
]
|
||||
role: 'close',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Help',
|
||||
@ -193,16 +203,16 @@ function createMenu({nativefierVersion, appQuit, zoomIn, zoomOut, zoomReset, zoo
|
||||
label: `Built with Nativefier v${nativefierVersion}`,
|
||||
click: () => {
|
||||
shell.openExternal('https://github.com/jiahaog/nativefier');
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Report an Issue',
|
||||
click: () => {
|
||||
shell.openExternal('https://github.com/jiahaog/nativefier/issues');
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
if (disableDevTools) {
|
||||
@ -218,45 +228,45 @@ function createMenu({nativefierVersion, appQuit, zoomIn, zoomOut, zoomReset, zoo
|
||||
{
|
||||
label: 'Services',
|
||||
role: 'services',
|
||||
submenu: []
|
||||
submenu: [],
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
type: 'separator',
|
||||
},
|
||||
{
|
||||
label: 'Hide App',
|
||||
accelerator: 'Command+H',
|
||||
role: 'hide'
|
||||
role: 'hide',
|
||||
},
|
||||
{
|
||||
label: 'Hide Others',
|
||||
accelerator: 'Command+Shift+H',
|
||||
role: 'hideothers'
|
||||
role: 'hideothers',
|
||||
},
|
||||
{
|
||||
label: 'Show All',
|
||||
role: 'unhide'
|
||||
role: 'unhide',
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
type: 'separator',
|
||||
},
|
||||
{
|
||||
label: 'Quit',
|
||||
accelerator: 'Command+Q',
|
||||
click: () => {
|
||||
appQuit();
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
template[3].submenu.push(
|
||||
{
|
||||
type: 'separator'
|
||||
type: 'separator',
|
||||
},
|
||||
{
|
||||
label: 'Bring All to Front',
|
||||
role: 'front'
|
||||
}
|
||||
role: 'front',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -19,12 +19,12 @@ function isWindows() {
|
||||
|
||||
function linkIsInternal(currentUrl, newUrl, internalUrlRegex) {
|
||||
if (internalUrlRegex) {
|
||||
var regex = RegExp(internalUrlRegex);
|
||||
const regex = RegExp(internalUrlRegex);
|
||||
return regex.test(newUrl);
|
||||
}
|
||||
|
||||
var currentDomain = wurl('domain', currentUrl);
|
||||
var newDomain = wurl('domain', newUrl);
|
||||
const currentDomain = wurl('domain', currentUrl);
|
||||
const newDomain = wurl('domain', newUrl);
|
||||
return currentDomain === newDomain;
|
||||
}
|
||||
|
||||
@ -51,6 +51,7 @@ function debugLog(browserWindow, message) {
|
||||
setTimeout(() => {
|
||||
browserWindow.webContents.send('debug', message);
|
||||
}, 3000);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
@ -61,5 +62,5 @@ export default {
|
||||
linkIsInternal,
|
||||
getCssToInject,
|
||||
debugLog,
|
||||
shouldInjectCss
|
||||
shouldInjectCss,
|
||||
};
|
||||
|
@ -4,22 +4,6 @@ import helpers from './helpers';
|
||||
|
||||
const { isOSX, isWindows, isLinux } = helpers;
|
||||
|
||||
function inferFlash() {
|
||||
if (isOSX()) {
|
||||
return darwinMatch();
|
||||
}
|
||||
|
||||
if (isWindows()) {
|
||||
return windowsMatch();
|
||||
}
|
||||
|
||||
if (isLinux()) {
|
||||
return linuxMatch();
|
||||
}
|
||||
|
||||
console.warn('Unable to determine OS to infer flash player');
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously find a file or directory
|
||||
* @param {RegExp} pattern regex
|
||||
@ -27,7 +11,7 @@ function inferFlash() {
|
||||
* @param {boolean} [findDir] if true, search results will be limited to only directories
|
||||
* @returns {Array}
|
||||
*/
|
||||
function findSync(pattern, base, findDir) {
|
||||
function findSync(pattern, basePath, findDir) {
|
||||
const matches = [];
|
||||
|
||||
(function findSyncRecurse(base) {
|
||||
@ -41,7 +25,7 @@ function findSync(pattern, base, findDir) {
|
||||
throw exception;
|
||||
}
|
||||
|
||||
children.forEach(child => {
|
||||
children.forEach((child) => {
|
||||
const childPath = path.join(base, child);
|
||||
const childIsDirectory = fs.lstatSync(childPath).isDirectory();
|
||||
const patternMatches = pattern.test(childPath);
|
||||
@ -63,7 +47,7 @@ function findSync(pattern, base, findDir) {
|
||||
matches.push(childPath);
|
||||
}
|
||||
});
|
||||
})(base);
|
||||
}(basePath));
|
||||
return matches;
|
||||
}
|
||||
|
||||
@ -79,4 +63,20 @@ function darwinMatch() {
|
||||
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;
|
||||
|
@ -2,11 +2,12 @@ import 'source-map-support/register';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { app, crashReporter } from 'electron';
|
||||
import electronDownload from 'electron-dl';
|
||||
|
||||
import createLoginWindow from './components/login/loginWindow';
|
||||
import createMainWindow from './components/mainWindow/mainWindow';
|
||||
import helpers from './helpers/helpers';
|
||||
import inferFlash from './helpers/inferFlash';
|
||||
import electronDownload from 'electron-dl';
|
||||
|
||||
const { isOSX } = helpers;
|
||||
|
||||
@ -67,7 +68,7 @@ if (appArgs.crashReporter) {
|
||||
crashReporter.start({
|
||||
productName: appArgs.name,
|
||||
submitURL: appArgs.crashReporter,
|
||||
autoSubmit: true
|
||||
autoSubmit: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -90,7 +91,6 @@ if (appArgs.singleInstance) {
|
||||
mainWindow.restore();
|
||||
}
|
||||
mainWindow.focus();
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
2
app/src/static/.eslintrc.yml
Normal file
2
app/src/static/.eslintrc.yml
Normal file
@ -0,0 +1,2 @@
|
||||
env:
|
||||
browser: true
|
@ -1,9 +1,10 @@
|
||||
import electron from 'electron';
|
||||
|
||||
const { ipcRenderer } = electron;
|
||||
|
||||
const form = document.getElementById('login-form');
|
||||
|
||||
form.addEventListener('submit', event => {
|
||||
form.addEventListener('submit', (event) => {
|
||||
event.preventDefault();
|
||||
const username = document.getElementById('username-input').value;
|
||||
const password = document.getElementById('password-input').value;
|
||||
|
@ -7,14 +7,45 @@ import fs from 'fs';
|
||||
|
||||
const INJECT_JS_PATH = path.join(__dirname, '../../', 'inject/inject.js');
|
||||
|
||||
/**
|
||||
* Patches window.Notification to set a callback on a new Notification
|
||||
* @param callback
|
||||
*/
|
||||
function setNotificationCallback(callback) {
|
||||
const OldNotify = window.Notification;
|
||||
const newNotify = (title, opt) => {
|
||||
callback(title, opt);
|
||||
return new OldNotify(title, opt);
|
||||
};
|
||||
newNotify.requestPermission = OldNotify.requestPermission.bind(OldNotify);
|
||||
Object.defineProperty(newNotify, 'permission', {
|
||||
get: () => OldNotify.permission,
|
||||
});
|
||||
|
||||
window.Notification = newNotify;
|
||||
}
|
||||
|
||||
function clickSelector(element) {
|
||||
const mouseEvent = new MouseEvent('click');
|
||||
element.dispatchEvent(mouseEvent);
|
||||
}
|
||||
|
||||
function injectScripts() {
|
||||
const needToInject = fs.existsSync(INJECT_JS_PATH);
|
||||
if (!needToInject) {
|
||||
return;
|
||||
}
|
||||
// Dynamically require scripts
|
||||
// eslint-disable-next-line global-require, import/no-dynamic-require
|
||||
require(INJECT_JS_PATH);
|
||||
}
|
||||
|
||||
setNotificationCallback((title, opt) => {
|
||||
ipcRenderer.send('notification', title, opt);
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// do things
|
||||
|
||||
window.addEventListener('contextmenu', event => {
|
||||
window.addEventListener('contextmenu', (event) => {
|
||||
event.preventDefault();
|
||||
let targetElement = event.srcElement;
|
||||
|
||||
@ -44,6 +75,7 @@ ipcRenderer.on('params', (event, message) => {
|
||||
});
|
||||
|
||||
ipcRenderer.on('debug', (event, message) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('debug:', message);
|
||||
});
|
||||
|
||||
@ -51,36 +83,3 @@ ipcRenderer.on('change-zoom', (event, message) => {
|
||||
webFrame.setZoomFactor(message);
|
||||
});
|
||||
|
||||
/**
|
||||
* Patches window.Notification to set a callback on a new Notification
|
||||
* @param callback
|
||||
*/
|
||||
function setNotificationCallback(callback) {
|
||||
|
||||
const OldNotify = window.Notification;
|
||||
const newNotify = (title, opt) => {
|
||||
callback(title, opt);
|
||||
return new OldNotify(title, opt);
|
||||
};
|
||||
newNotify.requestPermission = OldNotify.requestPermission.bind(OldNotify);
|
||||
Object.defineProperty(newNotify, 'permission', {
|
||||
get: () => {
|
||||
return OldNotify.permission;
|
||||
}
|
||||
});
|
||||
|
||||
window.Notification = newNotify;
|
||||
}
|
||||
|
||||
function clickSelector(element) {
|
||||
const mouseEvent = new MouseEvent('click');
|
||||
element.dispatchEvent(mouseEvent);
|
||||
}
|
||||
|
||||
function injectScripts() {
|
||||
const needToInject = fs.existsSync(INJECT_JS_PATH);
|
||||
if (!needToInject) {
|
||||
return;
|
||||
}
|
||||
require(INJECT_JS_PATH);
|
||||
}
|
||||
|
@ -1,14 +1,13 @@
|
||||
import gulp from 'gulp';
|
||||
import PATHS from './helpers/src-paths';
|
||||
|
||||
import del from 'del';
|
||||
import runSequence from 'run-sequence';
|
||||
import PATHS from './helpers/src-paths';
|
||||
|
||||
gulp.task('build', callback => {
|
||||
gulp.task('build', (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.APP_DEST).then(() => {
|
||||
del(PATHS.TEST_DEST).then(() => {
|
||||
|
@ -1,10 +1,9 @@
|
||||
import gulp from 'gulp';
|
||||
import webpack from 'webpack-stream';
|
||||
import PATHS from './../helpers/src-paths';
|
||||
|
||||
import webpack from 'webpack-stream';
|
||||
const webpackConfig = require('./../../webpack.config.js');
|
||||
|
||||
gulp.task('build-app', ['build-static'], () => {
|
||||
return gulp.src(PATHS.APP_MAIN_JS)
|
||||
.pipe(webpack(require('./../../webpack.config.js')))
|
||||
.pipe(gulp.dest(PATHS.APP_DEST));
|
||||
});
|
||||
gulp.task('build-app', ['build-static'], () => gulp.src(PATHS.APP_MAIN_JS)
|
||||
.pipe(webpack(webpackConfig))
|
||||
.pipe(gulp.dest(PATHS.APP_DEST)));
|
||||
|
@ -4,6 +4,4 @@ import helpers from './../helpers/gulp-helpers';
|
||||
|
||||
const { buildES6 } = helpers;
|
||||
|
||||
gulp.task('build-cli', done => {
|
||||
return buildES6(PATHS.CLI_SRC_JS, PATHS.CLI_DEST, done);
|
||||
});
|
||||
gulp.task('build-cli', done => buildES6(PATHS.CLI_SRC_JS, PATHS.CLI_DEST, done));
|
||||
|
@ -4,13 +4,9 @@ import helpers from './../helpers/gulp-helpers';
|
||||
|
||||
const { buildES6 } = helpers;
|
||||
|
||||
gulp.task('build-static-not-js', () => {
|
||||
return gulp.src([PATHS.APP_STATIC_ALL, '!**/*.js'])
|
||||
.pipe(gulp.dest(PATHS.APP_STATIC_DEST));
|
||||
});
|
||||
gulp.task('build-static-not-js', () => gulp.src([PATHS.APP_STATIC_ALL, '!**/*.js'])
|
||||
.pipe(gulp.dest(PATHS.APP_STATIC_DEST)));
|
||||
|
||||
gulp.task('build-static-js', done => {
|
||||
return buildES6(PATHS.APP_STATIC_JS, PATHS.APP_STATIC_DEST, done);
|
||||
});
|
||||
gulp.task('build-static-js', done => buildES6(PATHS.APP_STATIC_JS, PATHS.APP_STATIC_DEST, done));
|
||||
|
||||
gulp.task('build-static', ['build-static-js', 'build-static-not-js']);
|
||||
|
@ -4,7 +4,7 @@ import sourcemaps from 'gulp-sourcemaps';
|
||||
import babel from 'gulp-babel';
|
||||
|
||||
function shellExec(cmd, silent, callback) {
|
||||
shellJs.exec(cmd, {silent: silent}, (code, stdout, stderr) => {
|
||||
shellJs.exec(cmd, { silent }, (code, stdout, stderr) => {
|
||||
if (code) {
|
||||
callback(JSON.stringify({ code, stdout, stderr }));
|
||||
return;
|
||||
@ -24,5 +24,5 @@ function buildES6(src, dest, callback) {
|
||||
|
||||
export default {
|
||||
shellExec,
|
||||
buildES6
|
||||
buildES6,
|
||||
};
|
||||
|
@ -6,17 +6,17 @@ const paths = {
|
||||
CLI_SRC: 'src',
|
||||
CLI_DEST: 'lib',
|
||||
TEST_SRC: 'test',
|
||||
TEST_DEST: 'built-tests'
|
||||
TEST_DEST: 'built-tests',
|
||||
};
|
||||
|
||||
paths.APP_MAIN_JS = path.join(paths.APP_SRC, '/main.js');
|
||||
paths.APP_ALL = paths.APP_SRC + '/**/*';
|
||||
paths.APP_STATIC_ALL = path.join(paths.APP_SRC, 'static') + '/**/*';
|
||||
paths.APP_STATIC_JS = path.join(paths.APP_SRC, 'static') + '/**/*.js';
|
||||
paths.APP_ALL = `${paths.APP_SRC}/**/*`;
|
||||
paths.APP_STATIC_ALL = `${path.join(paths.APP_SRC, 'static')}/**/*`;
|
||||
paths.APP_STATIC_JS = `${path.join(paths.APP_SRC, 'static')}/**/*.js`;
|
||||
paths.APP_STATIC_DEST = path.join(paths.APP_DEST, 'static');
|
||||
paths.CLI_SRC_JS = paths.CLI_SRC + '/**/*.js';
|
||||
paths.CLI_DEST_JS = paths.CLI_DEST + '/**/*.js';
|
||||
paths.TEST_SRC_JS = paths.TEST_SRC + '/**/*.js';
|
||||
paths.TEST_DEST_JS = paths.TEST_DEST + '/**/*.js';
|
||||
paths.CLI_SRC_JS = `${paths.CLI_SRC}/**/*.js`;
|
||||
paths.CLI_DEST_JS = `${paths.CLI_DEST}/**/*.js`;
|
||||
paths.TEST_SRC_JS = `${paths.TEST_SRC}/**/*.js`;
|
||||
paths.TEST_DEST_JS = `${paths.TEST_DEST}/**/*.js`;
|
||||
|
||||
export default paths;
|
||||
|
@ -4,10 +4,8 @@ import helpers from './helpers/gulp-helpers';
|
||||
|
||||
const { shellExec } = helpers;
|
||||
|
||||
gulp.task('publish', done => {
|
||||
gulp.task('publish', (done) => {
|
||||
shellExec('npm publish', false, done);
|
||||
});
|
||||
|
||||
gulp.task('release', callback => {
|
||||
return runSequence('build', 'publish', callback);
|
||||
});
|
||||
gulp.task('release', callback => runSequence('build', 'publish', callback));
|
||||
|
@ -4,10 +4,8 @@ import helpers from './helpers/gulp-helpers';
|
||||
|
||||
const { shellExec } = helpers;
|
||||
|
||||
gulp.task('prune', done => {
|
||||
gulp.task('prune', (done) => {
|
||||
shellExec('npm prune', true, done);
|
||||
});
|
||||
|
||||
gulp.task('test', callback => {
|
||||
return runSequence('prune', 'mocha', callback);
|
||||
});
|
||||
gulp.task('test', callback => runSequence('prune', 'mocha', callback));
|
||||
|
@ -4,6 +4,4 @@ import helpers from './../helpers/gulp-helpers';
|
||||
|
||||
const { buildES6 } = helpers;
|
||||
|
||||
gulp.task('build-tests', done => {
|
||||
return buildES6(PATHS.TEST_SRC_JS, PATHS.TEST_DEST, done);
|
||||
});
|
||||
gulp.task('build-tests', done => buildES6(PATHS.TEST_SRC_JS, PATHS.TEST_DEST, done));
|
||||
|
@ -1,32 +1,27 @@
|
||||
import gulp from 'gulp';
|
||||
import PATHS from './../helpers/src-paths';
|
||||
|
||||
import istanbul from 'gulp-istanbul';
|
||||
import { Instrumenter } from 'isparta';
|
||||
import mocha from 'gulp-mocha';
|
||||
import PATHS from './../helpers/src-paths';
|
||||
|
||||
gulp.task('mocha', done => {
|
||||
gulp.task('mocha', (done) => {
|
||||
gulp.src([PATHS.CLI_SRC_JS, '!src/cli.js'])
|
||||
.pipe(istanbul({
|
||||
instrumenter: Instrumenter,
|
||||
includeUntested: true
|
||||
includeUntested: true,
|
||||
}))
|
||||
.pipe(istanbul.hookRequire()) // Force `require` to return covered files
|
||||
.on('finish', () => {
|
||||
return gulp.src(PATHS.TEST_SRC, {read: false})
|
||||
.on('finish', () => gulp.src(PATHS.TEST_SRC, { read: false })
|
||||
.pipe(mocha({
|
||||
compilers: 'js:babel-core/register',
|
||||
recursive: true
|
||||
recursive: true,
|
||||
}))
|
||||
.pipe(istanbul.writeReports({
|
||||
dir: './coverage',
|
||||
reporters: ['lcov'],
|
||||
reportOpts: {dir: './coverage'}
|
||||
reportOpts: { dir: './coverage' },
|
||||
}))
|
||||
.on('end', done);
|
||||
});
|
||||
.on('end', done));
|
||||
});
|
||||
|
||||
gulp.task('tdd', ['mocha'], () => {
|
||||
return gulp.watch(['src/**/*.js', 'test/**/*.js'], ['mocha']);
|
||||
});
|
||||
gulp.task('tdd', ['mocha'], () => gulp.watch(['src/**/*.js', 'test/**/*.js'], ['mocha']));
|
||||
|
@ -2,7 +2,7 @@ import gulp from 'gulp';
|
||||
import PATHS from './helpers/src-paths';
|
||||
|
||||
gulp.task('watch', ['build'], () => {
|
||||
var handleError = function(error) {
|
||||
const handleError = function (error) {
|
||||
console.error(error);
|
||||
};
|
||||
gulp.watch(PATHS.APP_ALL, ['build-app'])
|
||||
|
@ -3,7 +3,7 @@ import requireDir from 'require-dir';
|
||||
|
||||
requireDir('./gulp', {
|
||||
recurse: true,
|
||||
duplicates: true
|
||||
duplicates: true,
|
||||
});
|
||||
|
||||
gulp.task('default', ['build']);
|
||||
|
@ -15,6 +15,7 @@
|
||||
"test": "gulp test",
|
||||
"tdd": "gulp tdd",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"ci": "gulp build test && npm run lint",
|
||||
"clean": "gulp clean",
|
||||
"build": "gulp build",
|
||||
@ -68,8 +69,9 @@
|
||||
"babel-register": "^6.6.0",
|
||||
"chai": "^3.4.1",
|
||||
"del": "^2.2.0",
|
||||
"eslint": "^2.10.2",
|
||||
"eslint-config-google": "^0.5.0",
|
||||
"eslint": "^3.19.0",
|
||||
"eslint-config-airbnb-base": "^11.1.3",
|
||||
"eslint-plugin-import": "^2.2.0",
|
||||
"gulp": "^3.9.0",
|
||||
"gulp-babel": "^6.1.1",
|
||||
"gulp-istanbul": "^1.1.1",
|
||||
|
@ -6,91 +6,9 @@ import ncp from 'ncp';
|
||||
|
||||
const copy = ncp.ncp;
|
||||
|
||||
/**
|
||||
* Creates a temporary directory and copies the './app folder' inside, and adds a text file with the configuration
|
||||
* for the single page app.
|
||||
*
|
||||
* @param {string} src
|
||||
* @param {string} dest
|
||||
* @param {{}} options
|
||||
* @param callback
|
||||
*/
|
||||
function buildApp(src, dest, options, callback) {
|
||||
const appArgs = selectAppArgs(options);
|
||||
copy(src, dest, error => {
|
||||
if (error) {
|
||||
callback(`Error Copying temporary directory: ${error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.join(dest, '/nativefier.json'), JSON.stringify(appArgs));
|
||||
|
||||
maybeCopyScripts(options.inject, dest)
|
||||
.catch(error => {
|
||||
console.warn(error);
|
||||
})
|
||||
.then(() => {
|
||||
changeAppPackageJsonName(dest, appArgs.name, appArgs.targetUrl);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function maybeCopyScripts(srcs, dest) {
|
||||
if (!srcs) {
|
||||
return new Promise(resolve => {
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
const promises = srcs.map(src => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!fs.existsSync(src)) {
|
||||
reject('Error copying injection files: file not found');
|
||||
return;
|
||||
}
|
||||
|
||||
let destFileName;
|
||||
if (path.extname(src) === '.js') {
|
||||
destFileName = 'inject.js';
|
||||
} else if (path.extname(src) === '.css') {
|
||||
destFileName = 'inject.css';
|
||||
} else {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
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 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 {
|
||||
@ -118,17 +36,97 @@ function selectAppArgs(options) {
|
||||
zoom: options.zoom,
|
||||
internalUrls: options.internalUrls,
|
||||
crashReporter: options.crashReporter,
|
||||
singleInstance: options.singleInstance
|
||||
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
|
||||
let hash = crypto.createHash('md5');
|
||||
const hash = crypto.createHash('md5');
|
||||
hash.update(url);
|
||||
const postFixHash = hash.digest('hex').substring(0, 6);
|
||||
const normalized = _.kebabCase(appName.toLowerCase());
|
||||
return `${normalized}-nativefier-${postFixHash}`;
|
||||
}
|
||||
|
||||
function changeAppPackageJsonName(appPath, name, url) {
|
||||
const packageJsonPath = path.join(appPath, '/package.json');
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath));
|
||||
packageJson.name = normalizeAppName(name, url);
|
||||
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a temporary directory and copies the './app folder' inside,
|
||||
* and adds a text file with the configuration for the single page app.
|
||||
*
|
||||
* @param {string} src
|
||||
* @param {string} dest
|
||||
* @param {{}} options
|
||||
* @param callback
|
||||
*/
|
||||
function buildApp(src, dest, options, callback) {
|
||||
const appArgs = selectAppArgs(options);
|
||||
copy(src, dest, (error) => {
|
||||
if (error) {
|
||||
callback(`Error Copying temporary directory: ${error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.join(dest, '/nativefier.json'), JSON.stringify(appArgs));
|
||||
|
||||
maybeCopyScripts(options.inject, dest)
|
||||
.catch((error) => {
|
||||
console.warn(error);
|
||||
})
|
||||
.then(() => {
|
||||
changeAppPackageJsonName(dest, appArgs.name, appArgs.targetUrl);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export default buildApp;
|
||||
|
@ -15,86 +15,6 @@ import buildApp from './buildApp';
|
||||
const copy = ncp.ncp;
|
||||
const isWindows = helpers.isWindows;
|
||||
|
||||
/**
|
||||
* @callback buildAppCallback
|
||||
* @param error
|
||||
* @param {string} appPath
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {{}} options
|
||||
* @param {buildAppCallback} callback
|
||||
*/
|
||||
function buildMain(options, callback) {
|
||||
// pre process app
|
||||
|
||||
const tmpObj = tmp.dirSync({unsafeCleanup: true});
|
||||
const tmpPath = tmpObj.name;
|
||||
|
||||
// todo check if this is still needed on later version of packager
|
||||
const packagerConsole = new PackagerConsole();
|
||||
|
||||
const progress = new DishonestProgress(5);
|
||||
|
||||
async.waterfall([
|
||||
callback => {
|
||||
progress.tick('inferring');
|
||||
optionsFactory(options, callback);
|
||||
},
|
||||
(options, callback) => {
|
||||
progress.tick('copying');
|
||||
buildApp(options.dir, tmpPath, options, error => {
|
||||
if (error) {
|
||||
callback(error);
|
||||
return;
|
||||
}
|
||||
// dir now correctly references the app folder to package
|
||||
options.dir = tmpPath;
|
||||
callback(null, options);
|
||||
});
|
||||
},
|
||||
(options, callback) => {
|
||||
progress.tick('icons');
|
||||
iconBuild(options, (error, optionsWithIcon) => {
|
||||
callback(null, optionsWithIcon);
|
||||
});
|
||||
},
|
||||
(options, callback) => {
|
||||
progress.tick('packaging');
|
||||
// maybe skip passing icon parameter to electron packager
|
||||
const packageOptions = maybeNoIconOption(options);
|
||||
|
||||
packagerConsole.override();
|
||||
|
||||
packager(packageOptions, (error, appPathArray) => {
|
||||
|
||||
// restore console.error
|
||||
packagerConsole.restore();
|
||||
|
||||
// pass options which still contains the icon to waterfall
|
||||
callback(error, options, appPathArray);
|
||||
});
|
||||
},
|
||||
(options, appPathArray, callback) => {
|
||||
progress.tick('finalizing');
|
||||
// somehow appPathArray is a 1 element array
|
||||
const appPath = getAppPath(appPathArray);
|
||||
if (!appPath) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
maybeCopyIcons(options, appPath, error => {
|
||||
callback(error, appPath);
|
||||
});
|
||||
}
|
||||
], (error, appPath) => {
|
||||
packagerConsole.playback();
|
||||
callback(error, appPath);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the app path array to determine if the packaging was completed successfully
|
||||
* @param appPathArray Result from electron-packager
|
||||
@ -115,7 +35,8 @@ function getAppPath(appPathArray) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the `icon` parameter from options if building for Windows while not on Windows and Wine is not installed
|
||||
* Removes the `icon` parameter from options if building for Windows while not on Windows
|
||||
* and Wine is not installed
|
||||
* @param options
|
||||
*/
|
||||
function maybeNoIconOption(options) {
|
||||
@ -151,9 +72,91 @@ function maybeCopyIcons(options, appPath, callback) {
|
||||
// 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 => {
|
||||
copy(options.icon, path.join(destIconPath, destFileName), (error) => {
|
||||
callback(error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @callback buildAppCallback
|
||||
* @param error
|
||||
* @param {string} appPath
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {{}} inpOptions
|
||||
* @param {buildAppCallback} callback
|
||||
*/
|
||||
function buildMain(inpOptions, callback) {
|
||||
const options = Object.assign({}, inpOptions);
|
||||
|
||||
// pre process app
|
||||
|
||||
const tmpObj = tmp.dirSync({ unsafeCleanup: true });
|
||||
const tmpPath = tmpObj.name;
|
||||
|
||||
// todo check if this is still needed on later version of packager
|
||||
const packagerConsole = new PackagerConsole();
|
||||
|
||||
const progress = new DishonestProgress(5);
|
||||
|
||||
async.waterfall([
|
||||
(callback) => {
|
||||
progress.tick('inferring');
|
||||
optionsFactory(options, callback);
|
||||
},
|
||||
(options, callback) => {
|
||||
progress.tick('copying');
|
||||
buildApp(options.dir, tmpPath, options, (error) => {
|
||||
if (error) {
|
||||
callback(error);
|
||||
return;
|
||||
}
|
||||
// Change the reference file for the Electron app to be the temporary path
|
||||
const newOptions = Object.assign({}, options, { dir: tmpPath });
|
||||
callback(null, newOptions);
|
||||
});
|
||||
},
|
||||
(options, callback) => {
|
||||
progress.tick('icons');
|
||||
iconBuild(options, (error, optionsWithIcon) => {
|
||||
callback(null, optionsWithIcon);
|
||||
});
|
||||
},
|
||||
(options, callback) => {
|
||||
progress.tick('packaging');
|
||||
// maybe skip passing icon parameter to electron packager
|
||||
const packageOptions = maybeNoIconOption(options);
|
||||
|
||||
packagerConsole.override();
|
||||
|
||||
packager(packageOptions, (error, appPathArray) => {
|
||||
// restore console.error
|
||||
packagerConsole.restore();
|
||||
|
||||
// pass options which still contains the icon to waterfall
|
||||
callback(error, options, appPathArray);
|
||||
});
|
||||
},
|
||||
(options, appPathArray, callback) => {
|
||||
progress.tick('finalizing');
|
||||
// somehow appPathArray is a 1 element array
|
||||
const appPath = getAppPath(appPathArray);
|
||||
if (!appPath) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
maybeCopyIcons(options, appPath, (error) => {
|
||||
callback(error, appPath);
|
||||
});
|
||||
},
|
||||
], (error, appPath) => {
|
||||
packagerConsole.playback();
|
||||
callback(error, appPath);
|
||||
});
|
||||
}
|
||||
|
||||
export default buildMain;
|
||||
|
@ -6,6 +6,18 @@ import iconShellHelpers from './../helpers/iconShellHelpers';
|
||||
const { isOSX } = helpers;
|
||||
const { convertToPng, convertToIco, convertToIcns } = iconShellHelpers;
|
||||
|
||||
function iconIsIco(iconPath) {
|
||||
return path.extname(iconPath) === '.ico';
|
||||
}
|
||||
|
||||
function iconIsPng(iconPath) {
|
||||
return path.extname(iconPath) === '.png';
|
||||
}
|
||||
|
||||
function iconIsIcns(iconPath) {
|
||||
return path.extname(iconPath) === '.icns';
|
||||
}
|
||||
|
||||
/**
|
||||
* @callback augmentIconsCallback
|
||||
* @param error
|
||||
@ -16,11 +28,11 @@ const {convertToPng, convertToIco, convertToIcns} = iconShellHelpers;
|
||||
* Will check and convert a `.png` to `.icns` if necessary and augment
|
||||
* options.icon with the result
|
||||
*
|
||||
* @param options will need options.platform and options.icon
|
||||
* @param inpOptions will need options.platform and options.icon
|
||||
* @param {augmentIconsCallback} callback
|
||||
*/
|
||||
function iconBuild(options, callback) {
|
||||
|
||||
function iconBuild(inpOptions, callback) {
|
||||
const options = Object.assign({}, inpOptions);
|
||||
const returnCallback = () => {
|
||||
callback(null, options);
|
||||
};
|
||||
@ -37,11 +49,11 @@ function iconBuild(options, callback) {
|
||||
}
|
||||
|
||||
convertToIco(options.icon)
|
||||
.then(outPath => {
|
||||
.then((outPath) => {
|
||||
options.icon = outPath;
|
||||
returnCallback();
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
log.warn('Skipping icon conversion to .ico', error);
|
||||
returnCallback();
|
||||
});
|
||||
@ -55,11 +67,11 @@ function iconBuild(options, callback) {
|
||||
}
|
||||
|
||||
convertToPng(options.icon)
|
||||
.then(outPath => {
|
||||
.then((outPath) => {
|
||||
options.icon = outPath;
|
||||
returnCallback();
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
log.warn('Skipping icon conversion to .png', error);
|
||||
returnCallback();
|
||||
});
|
||||
@ -78,26 +90,14 @@ function iconBuild(options, callback) {
|
||||
}
|
||||
|
||||
convertToIcns(options.icon)
|
||||
.then(outPath => {
|
||||
.then((outPath) => {
|
||||
options.icon = outPath;
|
||||
returnCallback();
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
log.warn('Skipping icon conversion to .icns', error);
|
||||
returnCallback();
|
||||
});
|
||||
}
|
||||
|
||||
function iconIsIco(iconPath) {
|
||||
return path.extname(iconPath) === '.ico';
|
||||
}
|
||||
|
||||
function iconIsPng(iconPath) {
|
||||
return path.extname(iconPath) === '.png';
|
||||
}
|
||||
|
||||
function iconIsIcns(iconPath) {
|
||||
return path.extname(iconPath) === '.icns';
|
||||
}
|
||||
|
||||
export default iconBuild;
|
||||
|
@ -1,11 +1,10 @@
|
||||
#! /usr/bin/env node
|
||||
|
||||
import 'source-map-support/register';
|
||||
|
||||
import path from 'path';
|
||||
import program from 'commander';
|
||||
import nativefier from './index';
|
||||
const packageJson = require(path.join('..', 'package'));
|
||||
|
||||
const packageJson = require('./../package');
|
||||
|
||||
function collect(val, memo) {
|
||||
memo.push(val);
|
||||
@ -13,11 +12,10 @@ function collect(val, memo) {
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
|
||||
program
|
||||
.version(packageJson.version)
|
||||
.arguments('<targetUrl> [dest]')
|
||||
.action(function(targetUrl, appDir) {
|
||||
.action((targetUrl, appDir) => {
|
||||
program.targetUrl = targetUrl;
|
||||
program.out = appDir;
|
||||
})
|
||||
|
@ -2,6 +2,7 @@ import shell from 'shelljs';
|
||||
import path from 'path';
|
||||
import tmp from 'tmp';
|
||||
import helpers from './helpers';
|
||||
|
||||
const isOSX = helpers.isOSX;
|
||||
tmp.setGracefulCleanup();
|
||||
|
||||
@ -29,8 +30,8 @@ function convertToIcns(pngSrc, icnsDest, callback) {
|
||||
if (stdOut.includes('icon.iconset:error') || exitCode) {
|
||||
if (exitCode) {
|
||||
callback({
|
||||
stdOut: stdOut,
|
||||
stdError: stdError
|
||||
stdOut,
|
||||
stdError,
|
||||
}, pngSrc);
|
||||
return;
|
||||
}
|
||||
|
@ -9,23 +9,26 @@ class DishonestProgress {
|
||||
incomplete: ' ',
|
||||
total: total * this.tickParts,
|
||||
width: 50,
|
||||
clear: true
|
||||
clear: true,
|
||||
});
|
||||
|
||||
this.tickingPrevious = {
|
||||
message: '',
|
||||
remainder: 0,
|
||||
interval: null
|
||||
interval: null,
|
||||
};
|
||||
}
|
||||
|
||||
tick(message) {
|
||||
|
||||
const {remainder: prevRemainder, message: prevMessage, interval: prevInterval} = this.tickingPrevious;
|
||||
const {
|
||||
remainder: prevRemainder,
|
||||
message: prevMessage,
|
||||
interval: prevInterval,
|
||||
} = this.tickingPrevious;
|
||||
|
||||
if (prevRemainder) {
|
||||
this.bar.tick(prevRemainder, {
|
||||
task: prevMessage
|
||||
task: prevMessage,
|
||||
});
|
||||
clearInterval(prevInterval);
|
||||
}
|
||||
@ -33,19 +36,19 @@ class DishonestProgress {
|
||||
const realRemainder = this.bar.total - this.bar.curr;
|
||||
if (realRemainder === this.tickParts) {
|
||||
this.bar.tick(this.tickParts, {
|
||||
task: message
|
||||
task: message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.bar.tick({
|
||||
task: message
|
||||
task: message,
|
||||
});
|
||||
|
||||
this.tickingPrevious = {
|
||||
message: message,
|
||||
message,
|
||||
remainder: this.tickParts,
|
||||
interval: null
|
||||
interval: null,
|
||||
};
|
||||
|
||||
this.tickingPrevious.remainder -= 1;
|
||||
@ -57,11 +60,10 @@ class DishonestProgress {
|
||||
}
|
||||
|
||||
this.bar.tick({
|
||||
task: message
|
||||
task: message,
|
||||
});
|
||||
this.tickingPrevious.remainder -= 1;
|
||||
}, 200);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,15 +14,15 @@ function isWindows() {
|
||||
function downloadFile(fileUrl) {
|
||||
return axios.get(
|
||||
fileUrl, {
|
||||
responseType: 'arraybuffer'
|
||||
responseType: 'arraybuffer',
|
||||
})
|
||||
.then(function(response) {
|
||||
.then((response) => {
|
||||
if (!response.data) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
data: response.data,
|
||||
ext: path.extname(fileUrl)
|
||||
ext: path.extname(fileUrl),
|
||||
};
|
||||
});
|
||||
}
|
||||
@ -56,7 +56,7 @@ function allowedIconFormats(platform) {
|
||||
formats.push('.ico');
|
||||
break;
|
||||
default:
|
||||
throw `function allowedIconFormats error: Unknown platform ${platform}`;
|
||||
throw new Error(`function allowedIconFormats error: Unknown platform ${platform}`);
|
||||
}
|
||||
return formats;
|
||||
}
|
||||
@ -90,7 +90,7 @@ function allowedIconFormats(platform) {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw `function allowedIconFormats error: Unknown platform ${platform}`;
|
||||
throw new Error(`function allowedIconFormats error: Unknown platform ${platform}`);
|
||||
}
|
||||
return formats;
|
||||
}
|
||||
@ -99,5 +99,5 @@ export default {
|
||||
isOSX,
|
||||
isWindows,
|
||||
downloadFile,
|
||||
allowedIconFormats
|
||||
allowedIconFormats,
|
||||
};
|
||||
|
@ -2,6 +2,7 @@ import shell from 'shelljs';
|
||||
import path from 'path';
|
||||
import tmp from 'tmp';
|
||||
import helpers from './helpers';
|
||||
|
||||
const { isWindows, isOSX } = helpers;
|
||||
|
||||
tmp.setGracefulCleanup();
|
||||
@ -10,7 +11,7 @@ const SCRIPT_PATHS = {
|
||||
singleIco: path.join(__dirname, '../..', 'bin/singleIco'),
|
||||
convertToPng: path.join(__dirname, '../..', 'bin/convertToPng'),
|
||||
convertToIco: path.join(__dirname, '../..', 'bin/convertToIco'),
|
||||
convertToIcns: path.join(__dirname, '../..', 'bin/convertToIcns')
|
||||
convertToIcns: path.join(__dirname, '../..', 'bin/convertToIcns'),
|
||||
};
|
||||
|
||||
/**
|
||||
@ -29,8 +30,8 @@ function iconShellHelper(shellScriptPath, icoSrc, dest) {
|
||||
shell.exec(`${shellScriptPath} ${icoSrc} ${dest}`, { silent: true }, (exitCode, stdOut, stdError) => {
|
||||
if (exitCode) {
|
||||
reject({
|
||||
stdOut: stdOut,
|
||||
stdError: stdError
|
||||
stdOut,
|
||||
stdError,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -74,5 +75,5 @@ export default {
|
||||
singleIco,
|
||||
convertToPng,
|
||||
convertToIco,
|
||||
convertToIcns
|
||||
convertToIcns,
|
||||
};
|
||||
|
@ -1,3 +1,4 @@
|
||||
// TODO: remove this file and use quiet mode of new version of electron packager
|
||||
class PackagerConsole {
|
||||
|
||||
constructor() {
|
||||
@ -12,6 +13,7 @@ class PackagerConsole {
|
||||
this.consoleError = console.error;
|
||||
|
||||
// need to bind because somehow when _log() is called this refers to console
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
console.error = this._log.bind(this);
|
||||
}
|
||||
|
||||
|
@ -10,56 +10,6 @@ tmp.setGracefulCleanup();
|
||||
|
||||
const GITCLOUD_SPACE_DELIMITER = '-';
|
||||
|
||||
function inferIconFromStore(targetUrl, platform) {
|
||||
const allowedFormats = allowedIconFormats(platform);
|
||||
|
||||
return gitCloud('http://jiahaog.com/nativefier-icons/')
|
||||
.then(fileIndex => {
|
||||
const iconWithScores = mapIconWithMatchScore(fileIndex, targetUrl);
|
||||
const maxScore = getMaxMatchScore(iconWithScores);
|
||||
|
||||
if (maxScore === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const matchingIcons = getMatchingIcons(iconWithScores, maxScore);
|
||||
|
||||
let matchingUrl;
|
||||
for (let format of allowedFormats) {
|
||||
for (let icon of matchingIcons) {
|
||||
if (icon.ext !== format) {
|
||||
continue;
|
||||
}
|
||||
matchingUrl = icon.url;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matchingUrl) {
|
||||
return null;
|
||||
}
|
||||
return downloadFile(matchingUrl);
|
||||
});
|
||||
}
|
||||
|
||||
function mapIconWithMatchScore(fileIndex, targetUrl) {
|
||||
const normalisedTargetUrl = targetUrl.toLowerCase();
|
||||
return fileIndex
|
||||
.map(item => {
|
||||
const itemWords = item.name.split(GITCLOUD_SPACE_DELIMITER);
|
||||
const score = itemWords.reduce((currentScore, word) => {
|
||||
if (normalisedTargetUrl.includes(word)) {
|
||||
return currentScore + 1;
|
||||
}
|
||||
return currentScore;
|
||||
}, 0);
|
||||
|
||||
return Object.assign({},
|
||||
item,
|
||||
{score}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function getMaxMatchScore(iconWithScores) {
|
||||
return iconWithScores.reduce((maxScore, currentIcon) => {
|
||||
const currentScore = currentIcon.score;
|
||||
@ -73,23 +23,55 @@ function getMaxMatchScore(iconWithScores) {
|
||||
/**
|
||||
* also maps ext to icon object
|
||||
*/
|
||||
function getMatchingIcons(iconWithScores, maxScore) {
|
||||
return iconWithScores
|
||||
.filter(item => {
|
||||
return item.score === maxScore;
|
||||
})
|
||||
.map(item => {
|
||||
return Object.assign(
|
||||
{},
|
||||
item,
|
||||
{ext: path.extname(item.url)}
|
||||
);
|
||||
function getMatchingIcons(iconsWithScores, maxScore) {
|
||||
return iconsWithScores
|
||||
.filter(item => item.score === maxScore)
|
||||
.map(item => Object.assign({}, item, { ext: path.extname(item.url) }));
|
||||
}
|
||||
|
||||
function 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 inferIconFromStore(targetUrl, platform) {
|
||||
const allowedFormats = new Set(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 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 => {
|
||||
fs.writeFile(outPath, data, (error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
@ -107,7 +89,7 @@ function inferFromPage(targetUrl, platform, outDir) {
|
||||
|
||||
// todo might want to pass list of preferences instead
|
||||
return pageIcon(targetUrl, { ext: preferredExt })
|
||||
.then(icon => {
|
||||
.then((icon) => {
|
||||
if (!icon) {
|
||||
return null;
|
||||
}
|
||||
@ -116,6 +98,7 @@ function inferFromPage(targetUrl, platform, outDir) {
|
||||
return writeFilePromise(outfilePath, icon.data);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} targetUrl
|
||||
@ -123,9 +106,8 @@ function inferFromPage(targetUrl, platform, outDir) {
|
||||
* @param {string} outDir
|
||||
*/
|
||||
function inferIconFromUrlToPath(targetUrl, platform, outDir) {
|
||||
|
||||
return inferIconFromStore(targetUrl, platform)
|
||||
.then(icon => {
|
||||
.then((icon) => {
|
||||
if (!icon) {
|
||||
return inferFromPage(targetUrl, platform, outDir);
|
||||
}
|
||||
|
@ -6,18 +6,18 @@ function inferPlatform() {
|
||||
return platform;
|
||||
}
|
||||
|
||||
throw `Untested platform ${platform} detected`;
|
||||
throw new Error(`Untested platform ${platform} detected`);
|
||||
}
|
||||
|
||||
function inferArch() {
|
||||
const arch = os.arch();
|
||||
if (arch !== 'ia32' && arch !== 'x64') {
|
||||
throw `Incompatible architecture ${arch} detected`;
|
||||
throw new Error(`Incompatible architecture ${arch} detected`);
|
||||
}
|
||||
return arch;
|
||||
}
|
||||
|
||||
export default {
|
||||
inferPlatform: inferPlatform,
|
||||
inferArch: inferArch
|
||||
inferPlatform,
|
||||
inferArch,
|
||||
};
|
||||
|
@ -9,8 +9,8 @@ function inferTitle(url) {
|
||||
url,
|
||||
headers: {
|
||||
// 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 }) => {
|
||||
|
@ -7,16 +7,17 @@ const DEFAULT_CHROME_VERSION = '56.0.2924.87';
|
||||
|
||||
function getChromeVersionForElectronVersion(electronVersion, url = ELECTRON_VERSIONS_URL) {
|
||||
return axios.get(url, { timeout: 5000 })
|
||||
.then(response => {
|
||||
.then((response) => {
|
||||
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 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)) {
|
||||
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];
|
||||
@ -36,16 +37,14 @@ export function getUserAgentString(chromeVersion, platform) {
|
||||
userAgent = `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`;
|
||||
break;
|
||||
default:
|
||||
throw 'Error invalid platform specified to getUserAgentString()';
|
||||
throw new Error('Error invalid platform specified to getUserAgentString()');
|
||||
}
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
function inferUserAgent(electronVersion, platform, url = ELECTRON_VERSIONS_URL) {
|
||||
return getChromeVersionForElectronVersion(electronVersion, url)
|
||||
.then(chromeVersion => {
|
||||
return getUserAgentString(chromeVersion, platform);
|
||||
})
|
||||
.then(chromeVersion => getUserAgentString(chromeVersion, platform))
|
||||
.catch(() => {
|
||||
log.warn(`Unable to infer chrome version for user agent, using ${DEFAULT_CHROME_VERSION}`);
|
||||
return getUserAgentString(DEFAULT_CHROME_VERSION, platform);
|
||||
|
@ -15,10 +15,10 @@ function normalizeUrl(testUrl) {
|
||||
const validatorOptions = {
|
||||
require_protocol: true,
|
||||
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)) {
|
||||
throw `Your Url: "${urlWithProtocol}" is invalid!`;
|
||||
throw new Error(`Your Url: "${urlWithProtocol}" is invalid!`);
|
||||
}
|
||||
return urlWithProtocol;
|
||||
}
|
||||
|
@ -15,9 +15,27 @@ const {inferPlatform, inferArch} = inferOs;
|
||||
|
||||
const PLACEHOLDER_APP_DIR = path.join(__dirname, '../../', 'app');
|
||||
const ELECTRON_VERSION = '1.6.6';
|
||||
|
||||
const DEFAULT_APP_NAME = 'APP';
|
||||
|
||||
function sanitizeFilename(platform, str) {
|
||||
let result = sanitizeFilenameLib(str);
|
||||
|
||||
// remove all non ascii or use default app name
|
||||
// eslint-disable-next-line no-control-regex
|
||||
result = result.replace(/[^\x00-\x7F]/g, '') || DEFAULT_APP_NAME;
|
||||
|
||||
// spaces will cause problems with Ubuntu when pinned to the dock
|
||||
if (platform === 'linux') {
|
||||
return _.kebabCase(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function sanitizeOptions(options) {
|
||||
const name = sanitizeFilename(options.platform, options.name);
|
||||
return Object.assign({}, options, { name });
|
||||
}
|
||||
|
||||
/**
|
||||
* @callback optionsCallback
|
||||
* @param error
|
||||
@ -30,7 +48,6 @@ const DEFAULT_APP_NAME = 'APP';
|
||||
* @param {optionsCallback} callback
|
||||
*/
|
||||
function optionsFactory(inpOptions, callback) {
|
||||
|
||||
const options = {
|
||||
dir: PLACEHOLDER_APP_DIR,
|
||||
name: inpOptions.name,
|
||||
@ -69,7 +86,7 @@ function optionsFactory(inpOptions, callback) {
|
||||
tmpdir: false,
|
||||
zoom: inpOptions.zoom || 1.0,
|
||||
internalUrls: inpOptions.internalUrls || null,
|
||||
singleInstance: inpOptions.singleInstance || false
|
||||
singleInstance: inpOptions.singleInstance || false,
|
||||
};
|
||||
|
||||
if (options.verbose) {
|
||||
@ -103,34 +120,34 @@ function optionsFactory(inpOptions, callback) {
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
callback => {
|
||||
(callback) => {
|
||||
if (options.userAgent) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
inferUserAgent(options.electronVersion, options.platform)
|
||||
.then(userAgent => {
|
||||
.then((userAgent) => {
|
||||
options.userAgent = userAgent;
|
||||
callback();
|
||||
})
|
||||
.catch(callback);
|
||||
},
|
||||
callback => {
|
||||
(callback) => {
|
||||
if (options.icon) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
inferIcon(options.targetUrl, options.platform)
|
||||
.then(pngPath => {
|
||||
.then((pngPath) => {
|
||||
options.icon = pngPath;
|
||||
callback();
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
log.warn('Cannot automatically retrieve the app icon:', error);
|
||||
callback();
|
||||
});
|
||||
},
|
||||
callback => {
|
||||
(callback) => {
|
||||
// length also checks if its the commanderJS function or a string
|
||||
if (options.name && options.name.length > 0) {
|
||||
callback();
|
||||
@ -138,35 +155,21 @@ function optionsFactory(inpOptions, callback) {
|
||||
}
|
||||
options.name = DEFAULT_APP_NAME;
|
||||
|
||||
inferTitle(options.targetUrl).then(pageTitle => {
|
||||
inferTitle(options.targetUrl).then((pageTitle) => {
|
||||
options.name = pageTitle;
|
||||
}).catch(error => {
|
||||
}).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;
|
||||
}
|
||||
], error => {
|
||||
callback(error, sanitizeOptions(options));
|
||||
callback(null, 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;
|
||||
|
2
test/module/.eslintrc.yml
Normal file
2
test/module/.eslintrc.yml
Normal file
@ -0,0 +1,2 @@
|
||||
env:
|
||||
mocha: true
|
@ -7,7 +7,7 @@ import os from 'os';
|
||||
import path from 'path';
|
||||
import convertToIcns from './../../lib/helpers/convertToIcns';
|
||||
|
||||
let assert = chai.assert;
|
||||
const assert = chai.assert;
|
||||
|
||||
// Prerequisite for test: to use OSX with sips, iconutil and imagemagick convert
|
||||
|
||||
@ -18,24 +18,24 @@ function testConvertPng(pngName, done) {
|
||||
return;
|
||||
}
|
||||
|
||||
let stat = fs.statSync(icnsPath);
|
||||
const stat = fs.statSync(icnsPath);
|
||||
assert.isTrue(stat.isFile(), 'Output icns file should be a path');
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
describe('Get Icon Module', function() {
|
||||
it('Can convert icons', function() {
|
||||
describe('Get Icon Module', () => {
|
||||
it('Can convert icons', () => {
|
||||
if (os.platform() !== 'darwin') {
|
||||
console.warn('Skipping png conversion tests, OSX is required');
|
||||
return;
|
||||
}
|
||||
|
||||
it('Can convert a rgb png to icns', function(done) {
|
||||
it('Can convert a rgb png to icns', (done) => {
|
||||
testConvertPng('iconSample.png', done);
|
||||
});
|
||||
|
||||
it('Can convert a grey png to icns', function(done) {
|
||||
it('Can convert a grey png to icns', (done) => {
|
||||
testConvertPng('iconSampleGrey.png', done);
|
||||
});
|
||||
});
|
||||
|
@ -25,7 +25,7 @@ function checkApp(appPath, inputOptions, callback) {
|
||||
relPathToConfig = 'resources/app';
|
||||
break;
|
||||
default:
|
||||
throw 'Unknown app platform';
|
||||
throw new Error('Unknown app platform');
|
||||
}
|
||||
|
||||
const nativefierConfigPath = path.join(appPath, relPathToConfig, 'nativefier.json');
|
||||
@ -33,7 +33,8 @@ function checkApp(appPath, inputOptions, callback) {
|
||||
|
||||
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');
|
||||
// assert.strictEqual(inputOptions.appName, nativefierConfig.name,
|
||||
// 'Packaged app must have the same name as the input parameters');
|
||||
callback();
|
||||
} catch (exception) {
|
||||
callback(exception);
|
||||
@ -42,9 +43,8 @@ function checkApp(appPath, inputOptions, callback) {
|
||||
|
||||
describe('Nativefier Module', function () {
|
||||
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) => {
|
||||
|
||||
const tmpObj = tmp.dirSync({ unsafeCleanup: true });
|
||||
|
||||
const tmpPath = tmpObj.name;
|
||||
@ -53,7 +53,7 @@ describe('Nativefier Module', function() {
|
||||
targetUrl: 'http://google.com',
|
||||
out: tmpPath,
|
||||
overwrite: true,
|
||||
platform: null
|
||||
platform: null,
|
||||
};
|
||||
|
||||
options.platform = platform;
|
||||
@ -63,11 +63,11 @@ describe('Nativefier Module', function() {
|
||||
return;
|
||||
}
|
||||
|
||||
checkApp(appPath, options, error => {
|
||||
checkApp(appPath, options, (error) => {
|
||||
callback(error);
|
||||
});
|
||||
});
|
||||
}, error => {
|
||||
}, (error) => {
|
||||
done(error);
|
||||
});
|
||||
});
|
||||
|
@ -1,46 +1,44 @@
|
||||
import inferUserAgent from './../../lib/infer/inferUserAgent';
|
||||
import chai from 'chai';
|
||||
import _ from 'lodash';
|
||||
import inferUserAgent from './../../lib/infer/inferUserAgent';
|
||||
|
||||
const assert = chai.assert;
|
||||
|
||||
const TEST_RESULT = {
|
||||
darwin: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36',
|
||||
win32: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36',
|
||||
linux: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36'
|
||||
linux: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36',
|
||||
};
|
||||
|
||||
function testPlatform(platform) {
|
||||
return inferUserAgent('0.37.1', platform)
|
||||
.then(userAgent => {
|
||||
.then((userAgent) => {
|
||||
assert.equal(userAgent, TEST_RESULT[platform], 'Correct user agent should be inferred');
|
||||
});
|
||||
}
|
||||
|
||||
describe('Infer User Agent', function () {
|
||||
this.timeout(15000);
|
||||
it('Can infer userAgent for all platforms', function(done) {
|
||||
const testPromises = _.keys(TEST_RESULT).map(platform => {
|
||||
return testPlatform(platform);
|
||||
});
|
||||
it('Can infer userAgent for all platforms', (done) => {
|
||||
const testPromises = _.keys(TEST_RESULT).map(platform => testPlatform(platform));
|
||||
Promise
|
||||
.all(testPromises)
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((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/';
|
||||
inferUserAgent('1.6.7', 'darwin', TIMEOUT_URL)
|
||||
.then(userAgent => {
|
||||
.then((userAgent) => {
|
||||
assert.equal(
|
||||
userAgent,
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36',
|
||||
'Expect default user agent on connection error'
|
||||
'Expect default user agent on connection error',
|
||||
);
|
||||
done();
|
||||
})
|
||||
|
@ -1,10 +1,10 @@
|
||||
import normalizeUrl from '../../../src/options/normalizeUrl';
|
||||
import chai from 'chai';
|
||||
import normalizeUrl from '../../../src/options/normalizeUrl';
|
||||
|
||||
const assert = chai.assert;
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('Normalize URL', () => {
|
||||
|
||||
describe('given a valid URL without a protocol', () => {
|
||||
it('should allow the url', () => {
|
||||
assert.equal(normalizeUrl('http://www.google.com'), 'http://www.google.com');
|
||||
|
@ -1,24 +1,24 @@
|
||||
var electronPublicApi = ['electron'];
|
||||
const electronPublicApi = ['electron'];
|
||||
|
||||
var nodeModules = {};
|
||||
electronPublicApi.forEach(apiString => {
|
||||
nodeModules[apiString] = 'commonjs ' + apiString;
|
||||
const nodeModules = {};
|
||||
electronPublicApi.forEach((apiString) => {
|
||||
nodeModules[apiString] = `commonjs ${apiString}`;
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
target: 'node',
|
||||
output: {
|
||||
filename: 'main.js'
|
||||
filename: 'main.js',
|
||||
},
|
||||
node: {
|
||||
global: false,
|
||||
__dirname: false
|
||||
__dirname: false,
|
||||
},
|
||||
externals: nodeModules,
|
||||
module: {
|
||||
loaders: [
|
||||
{test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader'}
|
||||
]
|
||||
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' },
|
||||
],
|
||||
},
|
||||
devtool: 'source-map'
|
||||
devtool: 'source-map',
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user