mirror of
https://github.com/finos/SymphonyElectron.git
synced 2024-12-26 08:51:22 -06:00
add logging and refactor (#26)
This commit is contained in:
parent
c337c62e03
commit
40e6b173af
12
js/enums/logLevels.js
Normal file
12
js/enums/logLevels.js
Normal file
@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
var keyMirror = require('keymirror');
|
||||
|
||||
module.exports = keyMirror({
|
||||
ERROR: null,
|
||||
CONFLICT: null,
|
||||
WARN: null,
|
||||
ACTION: null,
|
||||
INFO: null,
|
||||
DEBUG: null
|
||||
});
|
28
js/log.js
Normal file
28
js/log.js
Normal file
@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
let logWindow;
|
||||
|
||||
/**
|
||||
* Send log messages from main process to logger hosted by
|
||||
* renderer process. Allows main process to use logger
|
||||
* provided by JS.
|
||||
* @param {enum} level enum from ./enums/LogLevel.js
|
||||
* @param {string} details msg to be logged
|
||||
*/
|
||||
function send(level, details) {
|
||||
if (logWindow && level && details) {
|
||||
logWindow.send('log', {
|
||||
logLevel: level,
|
||||
logDetails: details
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setLogWindow(win) {
|
||||
logWindow = win;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
send: send,
|
||||
setLogWindow: setLogWindow
|
||||
};
|
190
js/main.js
190
js/main.js
@ -1,190 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
const electron = require('electron');
|
||||
const packageJSON = require('../package.json');
|
||||
const menuTemplate = require('./menuTemplate.js');
|
||||
const path = require('path');
|
||||
const app = electron.app;
|
||||
const nodeURL = require('url');
|
||||
const getConfig = require('./getConfig.js');
|
||||
const { isMac, isDevEnv, getGuid } = require('./utils.js');
|
||||
const loadErrors = require('./dialogs/showLoadError.js');
|
||||
|
||||
// show dialog when certificate errors occur
|
||||
require('./dialogs/showCertError.js');
|
||||
|
||||
// Keep a global reference of the window object, if you don't, the window will
|
||||
// be closed automatically when the JavaScript object is garbage collected.
|
||||
let mainWindow;
|
||||
let windows = {};
|
||||
let willQuitApp = false;
|
||||
let isOnline = true;
|
||||
const { isMac } = require('./utils.js');
|
||||
|
||||
// exit early for squirrel installer
|
||||
if (require('electron-squirrel-startup')) {
|
||||
return;
|
||||
}
|
||||
|
||||
function createMainWindow (url) {
|
||||
let key = getGuid();
|
||||
require('./mainApiMgr.js');
|
||||
|
||||
mainWindow = new electron.BrowserWindow({
|
||||
title: 'Symphony',
|
||||
width: 1024, height: 768,
|
||||
show: true,
|
||||
webPreferences: {
|
||||
sandbox: true,
|
||||
nodeIntegration: false,
|
||||
preload: path.join(__dirname, '/preload.js'),
|
||||
winKey: key
|
||||
}
|
||||
});
|
||||
// monitor memory of main process
|
||||
require('./memoryMonitor.js');
|
||||
|
||||
function retry() {
|
||||
if (isOnline) {
|
||||
mainWindow.webContents && mainWindow.webContents.reload();
|
||||
} else {
|
||||
loadErrors.showNetworkConnectivityError(mainWindow, url, retry);
|
||||
}
|
||||
}
|
||||
|
||||
// content can be cached and will still finish load but
|
||||
// we might not have netowrk connectivity, so warn the user.
|
||||
mainWindow.webContents.once('did-finish-load', function() {
|
||||
if (!isOnline) {
|
||||
loadErrors.showNetworkConnectivityError(mainWindow, url, retry);
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.webContents.once('did-fail-load', function(event, errorCode,
|
||||
errorDesc, validatedURL, isMainFrame) {
|
||||
loadErrors.showLoadFailure(mainWindow, url, errorDesc, errorCode, retry);
|
||||
});
|
||||
|
||||
storeWindowKey(key, mainWindow);
|
||||
mainWindow.loadURL(url);
|
||||
|
||||
const menu = electron.Menu.buildFromTemplate(menuTemplate(app));
|
||||
electron.Menu.setApplicationMenu(menu);
|
||||
|
||||
mainWindow.on('close', function(e) {
|
||||
if (willQuitApp) {
|
||||
mainWindow = null;
|
||||
return;
|
||||
}
|
||||
// mac should hide window when hitting x close
|
||||
if (isMac) {
|
||||
mainWindow.hide();
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.on('closed', function () {
|
||||
// Dereference the window object, usually you would store windows
|
||||
// in an array if your app supports multi windows, this is the time
|
||||
// when you should delete the corresponding element.
|
||||
mainWindow = null;
|
||||
});
|
||||
|
||||
// open external links in default browser - window.open
|
||||
mainWindow.webContents.on('new-window', function(event, url) {
|
||||
event.preventDefault();
|
||||
electron.shell.openExternal(url);
|
||||
});
|
||||
}
|
||||
|
||||
function storeWindowKey(key, browserWin) {
|
||||
windows[key] = browserWin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure events comes from a window that we have created.
|
||||
* @param {EventEmitter} event node emitter event to be tested
|
||||
* @return {Boolean} returns true if exists otherwise false
|
||||
*/
|
||||
function isValidWindow(event) {
|
||||
if (event && event.sender) {
|
||||
// validate that event sender is from window we created
|
||||
let browserWin = electron.BrowserWindow.fromWebContents(event.sender);
|
||||
let winKey = event.sender.browserWindowOptions &&
|
||||
event.sender.browserWindowOptions.webPreferences &&
|
||||
event.sender.browserWindowOptions.webPreferences.winKey;
|
||||
|
||||
if (browserWin instanceof electron.BrowserWindow) {
|
||||
let win = windows[winKey];
|
||||
return win && win === browserWin;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only permit certain cmds for some windows
|
||||
* @param {EventEmitter} event node emitter event to be tested
|
||||
* @param {String} cmd cmd name
|
||||
* @return {Boolean} true if cmd is allowed for window, otherwise false
|
||||
*/
|
||||
function isCmdAllowed(event, cmd) {
|
||||
if (event && event.sender && cmd) {
|
||||
// validate that event sender is from window we created
|
||||
let browserWin = electron.BrowserWindow.fromWebContents(event.sender);
|
||||
|
||||
if (browserWin === mainWindow) {
|
||||
// allow all commands for main window
|
||||
return true;
|
||||
} else {
|
||||
// allow only certain cmds for child windows
|
||||
// e.g., open cmd not allowed for child windows
|
||||
return (arg.cmd !== 'open');
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle ipc messages from renderers. Only messages from windows we have
|
||||
* created are allowed.
|
||||
*/
|
||||
electron.ipcMain.on('symphony-msg', (event, arg) => {
|
||||
if (!isValidWindow(event)) {
|
||||
console.log('invalid window try to perform action, ignoring action.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isCmdAllowed(event, arg && arg.cmd)) {
|
||||
console.log('cmd not allowed for this window: ' + arg.cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg && arg.cmd === 'isOnline') {
|
||||
isOnline = arg.isOnline;
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg && arg.cmd === 'open' && arg.url) {
|
||||
let width = arg.width || 1024;
|
||||
let height = arg.height || 768;
|
||||
let title = arg.title || 'Symphony';
|
||||
let winKey = getGuid();
|
||||
|
||||
let childWindow = new electron.BrowserWindow({
|
||||
title: title,
|
||||
width: width,
|
||||
height: height,
|
||||
webPreferences: {
|
||||
sandbox: true,
|
||||
nodeIntegration: false,
|
||||
preload: path.join(__dirname, '/preload.js'),
|
||||
winKey: winKey
|
||||
}
|
||||
});
|
||||
|
||||
storeWindowKey(winKey, childWindow);
|
||||
childWindow.loadURL(arg.url);
|
||||
return;
|
||||
}
|
||||
});
|
||||
const windowMgr = require('./windowMgr.js');
|
||||
|
||||
/**
|
||||
* This method will be called when Electron has finished
|
||||
@ -206,17 +38,13 @@ function getUrlAndOpenMainWindow() {
|
||||
slahes: true,
|
||||
pathname: parsedUrl.href
|
||||
});
|
||||
createMainWindow(url);
|
||||
windowMgr.createMainWindow(url);
|
||||
}).catch(function(err) {
|
||||
let title = 'Error loading configuration';
|
||||
electron.dialog.showErrorBox(title, title + ': ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
app.on('before-quit', function() {
|
||||
willQuitApp = true;
|
||||
});
|
||||
|
||||
app.on('window-all-closed', function () {
|
||||
// On OS X it is common for applications and their menu bar
|
||||
// to stay active until the user quits explicitly with Cmd + Q
|
||||
@ -226,9 +54,9 @@ app.on('window-all-closed', function () {
|
||||
});
|
||||
|
||||
app.on('activate', function () {
|
||||
if (mainWindow === null) {
|
||||
if (windowMgr.isMainWindow(null)) {
|
||||
getUrlAndOpenMainWindow();
|
||||
} else {
|
||||
mainWindow.show();
|
||||
windowMgr.showMainWindow();
|
||||
}
|
||||
});
|
||||
|
96
js/mainApiMgr.js
Normal file
96
js/mainApiMgr.js
Normal file
@ -0,0 +1,96 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This module runs in the main process and handles api calls
|
||||
* from the renderer process.
|
||||
*/
|
||||
const electron = require('electron');
|
||||
const path = require('path');
|
||||
|
||||
const windowMgr = require('./windowMgr.js');
|
||||
const log = require('./log.js');
|
||||
|
||||
/**
|
||||
* Ensure events comes from a window that we have created.
|
||||
* @param {EventEmitter} event node emitter event to be tested
|
||||
* @return {Boolean} returns true if exists otherwise false
|
||||
*/
|
||||
function isValidWindow(event) {
|
||||
if (event && event.sender) {
|
||||
// validate that event sender is from window we created
|
||||
let browserWin = electron.BrowserWindow.fromWebContents(event.sender);
|
||||
let winKey = event.sender.browserWindowOptions &&
|
||||
event.sender.browserWindowOptions.webPreferences &&
|
||||
event.sender.browserWindowOptions.webPreferences.winKey;
|
||||
|
||||
return windowMgr.hasWindow(browserWin, winKey);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// only these cmds are allowed by main window
|
||||
let cmdBlackList = [ 'open', 'registerLogger' ];
|
||||
|
||||
/**
|
||||
* Only permit certain cmds for some windows
|
||||
* @param {EventEmitter} event node emitter event to be tested
|
||||
* @param {String} cmd cmd name
|
||||
* @return {Boolean} true if cmd is allowed for window, otherwise false
|
||||
*/
|
||||
function isCmdAllowed(event, cmd) {
|
||||
if (event && event.sender && cmd) {
|
||||
// validate that event sender is from window we created
|
||||
let browserWin = electron.BrowserWindow.fromWebContents(event.sender);
|
||||
|
||||
if (windowMgr.isMainWindow(browserWin)) {
|
||||
// allow all commands for main window
|
||||
return true;
|
||||
} else {
|
||||
// allow only certain cmds for child windows
|
||||
// e.g., open cmd not allowed for child windows
|
||||
return (cmdBlackList.indexOf(cmd) === -1)
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle API related ipc messages from renderers. Only messages from windows
|
||||
* we have created are allowed.
|
||||
*/
|
||||
electron.ipcMain.on('symphony-api', (event, arg) => {
|
||||
if (!isValidWindow(event)) {
|
||||
console.log('invalid window try to perform action, ignoring action.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isCmdAllowed(event, arg && arg.cmd)) {
|
||||
console.log('cmd not allowed for this window: ' + arg.cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!arg) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg.cmd === 'isOnline') {
|
||||
windowMgr.setIsOnline(arg.isOnline);
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg.cmd === 'registerLogger') {
|
||||
// renderer window that has a registered logger from JS.
|
||||
log.setLogWindow(event.sender);
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg.cmd === 'open' && arg.url) {
|
||||
let title = arg.title || 'Symphony';
|
||||
let width = arg.width || 1024;
|
||||
let height = arg.height || 768;
|
||||
windowMgr.createChildWindow(arg.url, title, width, height);
|
||||
return;
|
||||
}
|
||||
});
|
18
js/memoryMonitor.js
Normal file
18
js/memoryMonitor.js
Normal file
@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
const electron = require('electron');
|
||||
const log = require('./log.js');
|
||||
const logLevels = require('./enums/logLevels.js')
|
||||
|
||||
// once a minute
|
||||
setInterval(gatherMemory, 1000 * 60);
|
||||
|
||||
function gatherMemory() {
|
||||
var memory = process.getProcessMemoryInfo();
|
||||
var details =
|
||||
'workingSetSize: ' + memory.workingSetSize +
|
||||
' peakWorkingSetSize: ' + memory.peakWorkingSetSize +
|
||||
' privatesBytes: ' + memory.privatesBytes +
|
||||
' sharedBytes: ' + memory.sharedBytes;
|
||||
log.send(logLevels.INFO, details);
|
||||
}
|
@ -19,6 +19,8 @@ const local = {
|
||||
ipcRenderer: ipcRenderer
|
||||
};
|
||||
|
||||
const api = 'symphony-api';
|
||||
|
||||
// API exposed by Symphony to renderer processes:
|
||||
// Note: certain cmds are only allowed on some windows, this is checked by
|
||||
// main process.
|
||||
@ -27,21 +29,51 @@ window.SYM_API = {
|
||||
|
||||
// only allowed by main window - enforced by main process.
|
||||
openWindow: function(url) {
|
||||
local.ipcRenderer.send('symphony-msg', {
|
||||
local.ipcRenderer.send(api, {
|
||||
cmd: 'open',
|
||||
url: url
|
||||
});
|
||||
},
|
||||
networkStatusChange: function(isOnline) {
|
||||
local.ipcRenderer.send('symphony-msg', {
|
||||
cmd: 'isOnline',
|
||||
isOnline: isOnline
|
||||
});
|
||||
|
||||
/**
|
||||
* allows JS to register a logger that can be used by electron main process.
|
||||
* @param {Object} logger function that can be called accepting
|
||||
* object: {
|
||||
* logLevel: 'ERROR'|'CONFLICT'|'WARN'|'ACTION'|'INFO'|'DEBUG',
|
||||
* logDetails: String
|
||||
* }
|
||||
*
|
||||
* note: only main window is allowed to register a logger, others are
|
||||
* ignored.
|
||||
*/
|
||||
registerLogger: function(logger) {
|
||||
if (typeof logger === 'function') {
|
||||
local.logger = logger;
|
||||
|
||||
// only main window can register
|
||||
local.ipcRenderer.send(api, {
|
||||
cmd: 'registerLogger'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// listen for log message from main process
|
||||
local.ipcRenderer.on('log', (event, arg) => {
|
||||
console.log('got msg:' + arg)
|
||||
if (local.logger && arg && arg.level && arg.msg) {
|
||||
local.logger({
|
||||
logLevel: arg.level,
|
||||
logDetails: arg.msg
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function updateOnlineStatus() {
|
||||
window.SYM_API.networkStatusChange(navigator.onLine);
|
||||
local.ipcRenderer.send(api, {
|
||||
cmd: 'isOnline',
|
||||
isOnline: navigator.onLine
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('offline', updateOnlineStatus, false);
|
160
js/windowMgr.js
Normal file
160
js/windowMgr.js
Normal file
@ -0,0 +1,160 @@
|
||||
'use strict';
|
||||
|
||||
const electron = require('electron');
|
||||
const app = electron.app;
|
||||
const path = require('path');
|
||||
|
||||
const menuTemplate = require('./menuTemplate.js');
|
||||
const loadErrors = require('./dialogs/showLoadError.js');
|
||||
const { isMac, getGuid } = require('./utils.js');
|
||||
const log = require('./log.js')
|
||||
const logLevels = require('./enums/logLevels.js');
|
||||
|
||||
// show dialog when certificate errors occur
|
||||
require('./dialogs/showCertError.js');
|
||||
|
||||
// Keep a global reference of the window object, if you don't, the window will
|
||||
// be closed automatically when the JavaScript object is garbage collected.
|
||||
let mainWindow;
|
||||
let windows = {};
|
||||
let willQuitApp = false;
|
||||
let isOnline = true;
|
||||
const preloadScript = path.join(__dirname, '/RendererPreload.js');
|
||||
|
||||
function addWindowKey(key, browserWin) {
|
||||
windows[key] = browserWin;
|
||||
}
|
||||
|
||||
function removeWindowKey(key) {
|
||||
delete windows[key];
|
||||
}
|
||||
|
||||
function createMainWindow (url) {
|
||||
let key = getGuid();
|
||||
|
||||
mainWindow = new electron.BrowserWindow({
|
||||
title: 'Symphony',
|
||||
width: 1024, height: 768,
|
||||
show: true,
|
||||
webPreferences: {
|
||||
sandbox: true,
|
||||
nodeIntegration: false,
|
||||
preload: preloadScript,
|
||||
winKey: key
|
||||
}
|
||||
});
|
||||
|
||||
function retry() {
|
||||
if (isOnline) {
|
||||
mainWindow.webContents && mainWindow.webContents.reload();
|
||||
} else {
|
||||
loadErrors.showNetworkConnectivityError(mainWindow, url, retry);
|
||||
}
|
||||
}
|
||||
|
||||
// content can be cached and will still finish load but
|
||||
// we might not have netowrk connectivity, so warn the user.
|
||||
mainWindow.webContents.on('did-finish-load', function() {
|
||||
if (!isOnline) {
|
||||
loadErrors.showNetworkConnectivityError(mainWindow, url, retry);
|
||||
} else {
|
||||
log.send(logLevels.INFO, 'main window loaded');
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.webContents.on('did-fail-load', function(event, errorCode,
|
||||
errorDesc, validatedURL, isMainFrame) {
|
||||
loadErrors.showLoadFailure(mainWindow, url, errorDesc, errorCode, retry);
|
||||
});
|
||||
|
||||
addWindowKey(key, mainWindow);
|
||||
mainWindow.loadURL(url);
|
||||
|
||||
const menu = electron.Menu.buildFromTemplate(menuTemplate(app));
|
||||
electron.Menu.setApplicationMenu(menu);
|
||||
|
||||
mainWindow.on('close', function(e) {
|
||||
if (willQuitApp) {
|
||||
mainWindow = null;
|
||||
return;
|
||||
}
|
||||
// mac should hide window when hitting x close
|
||||
if (isMac) {
|
||||
mainWindow.hide();
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.on('closed', function () {
|
||||
removeWindowKey(key);
|
||||
mainWindow.removeAllEventListeners();
|
||||
// Dereference the window object, usually you would store windows
|
||||
// in an array if your app supports multi windows, this is the time
|
||||
// when you should delete the corresponding element.
|
||||
mainWindow = null;
|
||||
});
|
||||
|
||||
// open external links in default browser - window.open
|
||||
mainWindow.webContents.on('new-window', function(event, url) {
|
||||
event.preventDefault();
|
||||
electron.shell.openExternal(url);
|
||||
});
|
||||
}
|
||||
|
||||
app.on('before-quit', function() {
|
||||
willQuitApp = true;
|
||||
});
|
||||
|
||||
function showMainWindow() {
|
||||
mainWindow.show();
|
||||
}
|
||||
|
||||
function isMainWindow(win) {
|
||||
return mainWindow === win;
|
||||
}
|
||||
|
||||
function hasWindow(win, winKey) {
|
||||
if (win instanceof electron.BrowserWindow) {
|
||||
let browserWin = windows[winKey];
|
||||
return browserWin && win === browserWin;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function createChildWindow(url, title, width, height) {
|
||||
let winKey = getGuid();
|
||||
|
||||
let childWindow = new electron.BrowserWindow({
|
||||
title: title,
|
||||
width: width,
|
||||
height: height,
|
||||
webPreferences: {
|
||||
sandbox: true,
|
||||
nodeIntegration: false,
|
||||
preload: preloadScript,
|
||||
winKey: winKey
|
||||
}
|
||||
});
|
||||
|
||||
addWindowKey(winKey, childWindow);
|
||||
childWindow.loadURL(url);
|
||||
|
||||
childWindow.on('closed', function() {
|
||||
childWindow.removeAllEventListeners();
|
||||
removeWindowKey(winKey);
|
||||
});
|
||||
}
|
||||
|
||||
function setIsOnline(status) {
|
||||
isOnline = status;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createMainWindow: createMainWindow,
|
||||
showMainWindow: showMainWindow,
|
||||
isMainWindow: isMainWindow,
|
||||
hasWindow: hasWindow,
|
||||
createChildWindow: createChildWindow,
|
||||
setIsOnline: setIsOnline
|
||||
};
|
@ -62,6 +62,7 @@
|
||||
"jest": "^19.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"electron-squirrel-startup": "^1.0.0"
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"keymirror": "0.1.1"
|
||||
}
|
||||
}
|
||||
|
2
tests/README.md
Normal file
2
tests/README.md
Normal file
@ -0,0 +1,2 @@
|
||||
# to override dependencies
|
||||
- use https://github.com/thlorenz/proxyquire
|
Loading…
Reference in New Issue
Block a user