diff --git a/demo/index.html b/demo/index.html
index b0975352..d1818a2b 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -16,7 +16,7 @@
-
+
diff --git a/js/aboutApp/index.js b/js/aboutApp/index.js
index 3181bb92..117f86a6 100644
--- a/js/aboutApp/index.js
+++ b/js/aboutApp/index.js
@@ -6,6 +6,7 @@ const path = require('path');
const fs = require('fs');
const log = require('../log.js');
const logLevels = require('../enums/logLevels.js');
+const buildNumber = require('../../package.json').buildNumber;
let aboutWindow;
@@ -78,6 +79,10 @@ function openAboutWindow(windowName) {
aboutWindow.show();
});
+ aboutWindow.webContents.on('did-finish-load', () => {
+ aboutWindow.webContents.send('buildNumber', buildNumber || '0');
+ });
+
aboutWindow.on('close', () => {
destroyWindow();
});
diff --git a/js/aboutApp/renderer.js b/js/aboutApp/renderer.js
index 808c4337..dcb35981 100644
--- a/js/aboutApp/renderer.js
+++ b/js/aboutApp/renderer.js
@@ -1,5 +1,5 @@
'use strict';
-const { remote } = require('electron');
+const { remote, ipcRenderer } = require('electron');
renderDom();
@@ -9,13 +9,19 @@ renderDom();
function renderDom() {
document.addEventListener('DOMContentLoaded', function () {
const applicationName = remote.app.getName() || 'Symphony';
- const version = remote.app.getVersion();
let appName = document.getElementById('app-name');
- let versionText = document.getElementById('version');
let copyright = document.getElementById('copyright');
appName.innerHTML = applicationName;
- versionText.innerHTML = version ? `Version ${version} (${version})` : null;
copyright.innerHTML = `Copyright © ${new Date().getFullYear()} ${applicationName}`
});
}
+
+ipcRenderer.on('buildNumber', (event, buildNumber) => {
+ let versionText = document.getElementById('version');
+ const version = remote.app.getVersion();
+
+ if (versionText) {
+ versionText.innerHTML = version ? `Version ${version} (${version}.${buildNumber})` : 'N/A';
+ }
+});
\ No newline at end of file
diff --git a/js/basicAuth/basic-auth.html b/js/basicAuth/basic-auth.html
new file mode 100644
index 00000000..b81e7e80
--- /dev/null
+++ b/js/basicAuth/basic-auth.html
@@ -0,0 +1,91 @@
+
+
+
+
+ Authentication Request
+
+
+
+
+
Please provide your login credentials for:
+
hostname
+
+
+
+
\ No newline at end of file
diff --git a/js/basicAuth/index.js b/js/basicAuth/index.js
new file mode 100644
index 00000000..a9443695
--- /dev/null
+++ b/js/basicAuth/index.js
@@ -0,0 +1,126 @@
+'use strict';
+
+const electron = require('electron');
+const BrowserWindow = electron.BrowserWindow;
+const ipc = electron.ipcMain;
+const path = require('path');
+const fs = require('fs');
+const log = require('../log.js');
+const logLevels = require('../enums/logLevels.js');
+
+let basicAuthWindow;
+
+const local = {};
+
+let windowConfig = {
+ width: 360,
+ height: 270,
+ show: false,
+ modal: true,
+ autoHideMenuBar: true,
+ titleBarStyle: true,
+ resizable: false,
+ webPreferences: {
+ preload: path.join(__dirname, 'renderer.js'),
+ sandbox: true,
+ nodeIntegration: false
+ }
+};
+
+/**
+ * method to get the HTML template path
+ * @returns {string}
+ */
+function getTemplatePath() {
+ let templatePath = path.join(__dirname, 'basic-auth.html');
+ try {
+ fs.statSync(templatePath).isFile();
+ } catch (err) {
+ log.send(logLevels.ERROR, 'basic-auth: Could not find template ("' + templatePath + '").');
+ }
+ return 'file://' + templatePath;
+}
+
+/**
+ * Opens the basic auth window for authentication
+ * @param {String} windowName - name of the window upon which this window should show
+ * @param {String} hostname - name of the website that requires authentication
+ * @param {Function} callback
+ */
+function openBasicAuthWindow(windowName, hostname, callback) {
+
+ // Register callback function
+ if (typeof callback === 'function') {
+ local.authCallback = callback;
+ }
+
+ // This prevents creating multiple instances of the
+ // basic auth window
+ if (basicAuthWindow) {
+ if (basicAuthWindow.isMinimized()) {
+ basicAuthWindow.restore();
+ }
+ basicAuthWindow.focus();
+ return;
+ }
+ let allWindows = BrowserWindow.getAllWindows();
+ allWindows = allWindows.find((window) => { return window.winName === windowName });
+
+ // if we couldn't find any window matching the window name
+ // it will render as a new window
+ if (allWindows) {
+ windowConfig.parent = allWindows;
+ }
+
+ basicAuthWindow = new BrowserWindow(windowConfig);
+ basicAuthWindow.setVisibleOnAllWorkspaces(true);
+ basicAuthWindow.loadURL(getTemplatePath());
+
+ // sets the AlwaysOnTop property for the basic auth window
+ // if the main window's AlwaysOnTop is true
+ let focusedWindow = BrowserWindow.getFocusedWindow();
+ if (focusedWindow && focusedWindow.isAlwaysOnTop()) {
+ basicAuthWindow.setAlwaysOnTop(true);
+ }
+
+ basicAuthWindow.once('ready-to-show', () => {
+ basicAuthWindow.show();
+ });
+
+ basicAuthWindow.webContents.on('did-finish-load', () => {
+ basicAuthWindow.webContents.send('hostname', hostname);
+ });
+
+ basicAuthWindow.on('close', () => {
+ destroyWindow();
+ });
+
+ basicAuthWindow.on('closed', () => {
+ destroyWindow();
+ });
+}
+
+ipc.on('login', (event, args) => {
+ if (typeof args === 'object' && typeof local.authCallback === 'function') {
+ local.authCallback(args.username, args.password);
+ basicAuthWindow.close();
+ }
+});
+
+ipc.on('close-basic-auth', () => {
+ if (basicAuthWindow) {
+ basicAuthWindow.close();
+ }
+});
+
+/**
+ * Destroys a window
+ */
+function destroyWindow() {
+ basicAuthWindow = null;
+}
+
+
+module.exports = {
+ openBasicAuthWindow: openBasicAuthWindow
+};
\ No newline at end of file
diff --git a/js/basicAuth/renderer.js b/js/basicAuth/renderer.js
new file mode 100644
index 00000000..3e99dac7
--- /dev/null
+++ b/js/basicAuth/renderer.js
@@ -0,0 +1,55 @@
+'use strict';
+const electron = require('electron');
+const ipc = electron.ipcRenderer;
+
+renderDom();
+
+/**
+ * Method that renders application data
+ */
+function renderDom() {
+ document.addEventListener('DOMContentLoaded', function () {
+ loadContent();
+ });
+}
+
+function loadContent() {
+ let basicAuth = document.getElementById('basicAuth');
+ let cancel = document.getElementById('cancel');
+
+ if (basicAuth) {
+ basicAuth.onsubmit = (e) => {
+ e.preventDefault();
+ submitForm();
+ };
+ }
+
+ if (cancel) {
+ cancel.addEventListener('click', () => {
+ ipc.send('close-basic-auth');
+ });
+ }
+}
+
+/**
+ * Method that gets invoked on submitting the form
+ */
+function submitForm() {
+ let username = document.getElementById('username').value;
+ let password = document.getElementById('password').value;
+
+ if (username && password) {
+ ipc.send('login', { username, password });
+ }
+}
+
+/**
+ * Updates the hosts name
+ */
+ipc.on('hostname', (event, host) => {
+ let hostname = document.getElementById('hostname');
+
+ if (hostname){
+ hostname.innerHTML = host || 'unknown';
+ }
+});
\ No newline at end of file
diff --git a/js/config.js b/js/config.js
index ba92566f..36a75e8d 100644
--- a/js/config.js
+++ b/js/config.js
@@ -6,6 +6,8 @@ const path = require('path');
const fs = require('fs');
const AppDirectory = require('appdirectory');
const omit = require('lodash.omit');
+const pick = require('lodash.pick');
+const difference = require('lodash.difference');
const isDevEnv = require('./utils/misc.js').isDevEnv;
const isMac = require('./utils/misc.js').isMac;
@@ -314,6 +316,64 @@ function updateUserConfigMac() {
});
}
+/**
+ * Method that tries to grab multiple config field from user config
+ * if field doesn't exist tries reading from global config
+ *
+ * @param {Array} fieldNames - array of config filed names
+ * @returns {Promise} - object all the config data from user and global config
+ */
+function getMultipleConfigField(fieldNames) {
+ return new Promise((resolve, reject) => {
+ let userConfigData;
+
+ if (!fieldNames && fieldNames.length < 0) {
+ reject('cannot read config file, invalid fields');
+ return;
+ }
+
+ // reads user config data
+ readUserConfig().then((config) => {
+ userConfigData = pick(config, fieldNames);
+ let userConfigKeys = userConfigData ? Object.keys(userConfigData) : undefined;
+
+ /**
+ * Condition to validate data from user config,
+ * if all the required fields are not present
+ * this tries to fetch the remaining fields from global config
+ */
+ if (!userConfigKeys || userConfigKeys.length < fieldNames.length) {
+
+ // remainingConfig - config field that are not present in the user config
+ let remainingConfig = difference(fieldNames, userConfigKeys);
+
+ if (remainingConfig && Object.keys(remainingConfig).length > 0) {
+ readGlobalConfig().then((globalConfigData) => {
+ // assigns the remaining fields from global config to the user config
+ userConfigData = Object.assign(userConfigData, pick(globalConfigData, remainingConfig));
+ resolve(userConfigData);
+ }).catch((err) => {
+ reject(err);
+ });
+ }
+
+ } else {
+ resolve(userConfigData);
+ }
+ }).catch(() => {
+ // This reads global config if there was any
+ // error while reading user config
+ readGlobalConfig().then((config) => {
+ userConfigData = pick(config, fieldNames);
+ resolve(userConfigData);
+ }).catch((err) => {
+ reject(err);
+ });
+ });
+ });
+}
+
+
/**
* Clears the cached config
*/
@@ -331,6 +391,7 @@ module.exports = {
updateConfigField,
updateUserConfigWin,
updateUserConfigMac,
+ getMultipleConfigField,
// items below here are only exported for testing, do NOT use!
saveUserConfig,
diff --git a/js/dialogs/showBasicAuth.js b/js/dialogs/showBasicAuth.js
new file mode 100644
index 00000000..36c4b320
--- /dev/null
+++ b/js/dialogs/showBasicAuth.js
@@ -0,0 +1,25 @@
+'use strict';
+
+const electron = require('electron');
+
+const basicAuth = require('../basicAuth');
+
+/**
+ * Having a proxy or hosts that requires authentication will allow user to
+ * enter their credentials 'username' & 'password'
+ */
+electron.app.on('login', (event, webContents, request, authInfo, callback) => {
+
+ event.preventDefault();
+
+ // name of the host to display
+ let hostname = authInfo.host || authInfo.realm;
+ let browserWin = electron.BrowserWindow.fromWebContents(webContents);
+ let windowName = browserWin.winName || '';
+
+ /**
+ * Opens an electron modal window in which
+ * user can enter credentials fot the host
+ */
+ basicAuth.openBasicAuthWindow(windowName, hostname, callback);
+});
diff --git a/js/main.js b/js/main.js
index ed36388f..7a6e855a 100644
--- a/js/main.js
+++ b/js/main.js
@@ -11,6 +11,7 @@ const urlParser = require('url');
// Local Dependencies
const {getConfigField, updateUserConfigWin, updateUserConfigMac} = require('./config.js');
+const {setCheckboxValues} = require('./menus/menuTemplate.js');
const { isMac, isDevEnv } = require('./utils/misc.js');
const protocolHandler = require('./protocolHandler');
const getCmdLineArg = require('./utils/getCmdLineArg.js');
@@ -92,7 +93,7 @@ if (isMac) {
* initialization and is ready to create browser windows.
* Some APIs can only be used after this event occurs.
*/
-app.on('ready', setupThenOpenMainWindow);
+app.on('ready', readConfigThenOpenMainWindow);
/**
* Is triggered when all the windows are closed
@@ -127,6 +128,20 @@ app.on('open-url', function(event, url) {
handleProtocolAction(url);
});
+/**
+ * Reads the config fields that are required for the menu items
+ * then opens the main window
+ *
+ * This is a workaround for the issue where the menu template was returned
+ * even before the config data was populated
+ * https://perzoinc.atlassian.net/browse/ELECTRON-154
+ */
+function readConfigThenOpenMainWindow() {
+ setCheckboxValues()
+ .then(setupThenOpenMainWindow)
+ .catch(setupThenOpenMainWindow)
+}
+
/**
* Sets up the app (to handle various things like config changes, protocol handling etc.)
* and opens the main window
diff --git a/js/menus/menuTemplate.js b/js/menus/menuTemplate.js
index ab822603..1010426a 100644
--- a/js/menus/menuTemplate.js
+++ b/js/menus/menuTemplate.js
@@ -1,7 +1,7 @@
'use strict';
const electron = require('electron');
-const { getConfigField, updateConfigField } = require('../config.js');
+const { updateConfigField, getMultipleConfigField } = require('../config.js');
const AutoLaunch = require('auto-launch');
const isMac = require('../utils/misc.js').isMac;
const log = require('../log.js');
@@ -13,8 +13,6 @@ let minimizeOnClose = false;
let launchOnStartup = false;
let isAlwaysOnTop = false;
-setCheckboxValues();
-
let symphonyAutoLauncher;
if (isMac) {
@@ -266,39 +264,42 @@ function getTemplate(app) {
* based on configuration
*/
function setCheckboxValues() {
- getConfigField('minimizeOnClose').then(function(mClose) {
- minimizeOnClose = mClose;
- }).catch(function(err) {
- let title = 'Error loading configuration';
- log.send(logLevels.ERROR, 'MenuTemplate: error getting config field minimizeOnClose, error: ' + err);
- electron.dialog.showErrorBox(title, title + ': ' + err);
+ return new Promise((resolve) => {
+ /**
+ * Method that reads multiple config fields
+ */
+ getMultipleConfigField(['minimizeOnClose', 'launchOnStartup', 'alwaysOnTop', 'notificationSettings'])
+ .then(function (configData) {
+ for (let key in configData) {
+ if (configData.hasOwnProperty(key)) { // eslint-disable-line no-prototype-builtins
+ switch (key) {
+ case 'minimizeOnClose':
+ minimizeOnClose = configData[key];
+ break;
+ case 'launchOnStartup':
+ launchOnStartup = configData[key];
+ break;
+ case 'alwaysOnTop':
+ isAlwaysOnTop = configData[key];
+ eventEmitter.emit('isAlwaysOnTop', configData[key]);
+ break;
+ case 'notificationSettings':
+ eventEmitter.emit('notificationSettings', configData[key]);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return resolve();
+ })
+ .catch((err) => {
+ let title = 'Error loading configuration';
+ log.send(logLevels.ERROR, 'MenuTemplate: error reading configuration fields, error: ' + err);
+ electron.dialog.showErrorBox(title, title + ': ' + err);
+ return resolve();
+ });
});
-
- getConfigField('launchOnStartup').then(function(lStartup) {
- launchOnStartup = lStartup;
- }).catch(function(err) {
- let title = 'Error loading configuration';
- log.send(logLevels.ERROR, 'MenuTemplate: error getting config field launchOnStartup, error: ' + err);
- electron.dialog.showErrorBox(title, title + ': ' + err);
- });
-
- getConfigField('alwaysOnTop').then(function(mAlwaysOnTop) {
- isAlwaysOnTop = mAlwaysOnTop;
- eventEmitter.emit('isAlwaysOnTop', isAlwaysOnTop);
- }).catch(function(err) {
- let title = 'Error loading configuration';
- log.send(logLevels.ERROR, 'MenuTemplate: error getting config field alwaysOnTop, error: ' + err);
- electron.dialog.showErrorBox(title, title + ': ' + err);
- });
-
- getConfigField('notificationSettings').then(function(notfObject) {
- eventEmitter.emit('notificationSettings', notfObject);
- }).catch(function(err) {
- let title = 'Error loading configuration';
- log.send(logLevels.ERROR, 'MenuTemplate: error getting config field notificationSettings, error: ' + err);
- electron.dialog.showErrorBox(title, title + ': ' + err);
- });
-
}
function getMinimizeOnClose() {
@@ -307,5 +308,6 @@ function getMinimizeOnClose() {
module.exports = {
getTemplate: getTemplate,
- getMinimizeOnClose: getMinimizeOnClose
+ getMinimizeOnClose: getMinimizeOnClose,
+ setCheckboxValues: setCheckboxValues
};
\ No newline at end of file
diff --git a/js/notify/assets/symphony-logo-black.png b/js/notify/assets/symphony-logo-black.png
new file mode 100644
index 00000000..8ba73198
Binary files /dev/null and b/js/notify/assets/symphony-logo-black.png differ
diff --git a/js/notify/assets/symphony-logo-white.png b/js/notify/assets/symphony-logo-white.png
new file mode 100644
index 00000000..1a484192
Binary files /dev/null and b/js/notify/assets/symphony-logo-white.png differ
diff --git a/js/notify/electron-notify-preload.js b/js/notify/electron-notify-preload.js
index afdca896..589385f9 100644
--- a/js/notify/electron-notify-preload.js
+++ b/js/notify/electron-notify-preload.js
@@ -9,6 +9,8 @@
const electron = require('electron');
const ipc = electron.ipcRenderer;
+const whiteColorRegExp = new RegExp(/^(?:white|#fff(?:fff)?|rgba?\(\s*255\s*,\s*255\s*,\s*255\s*(?:,\s*1\s*)?\))$/i);
+
/**
* Sets style for a notification
* @param config
@@ -19,7 +21,9 @@ function setStyle(config) {
let container = notiDoc.getElementById('container');
let header = notiDoc.getElementById('header');
let image = notiDoc.getElementById('image');
+ let logo = notiDoc.getElementById('symphony-logo');
let title = notiDoc.getElementById('title');
+ let pod = notiDoc.getElementById('pod');
let message = notiDoc.getElementById('message');
let close = notiDoc.getElementById('close');
@@ -37,8 +41,12 @@ function setStyle(config) {
setStyleOnDomElement(config.defaultStyleImage, image);
+ setStyleOnDomElement(config.defaultStyleLogo, logo);
+
setStyleOnDomElement(config.defaultStyleTitle, title);
+ setStyleOnDomElement(config.defaultStylePod, pod);
+
setStyleOnDomElement(config.defaultStyleText, message);
setStyleOnDomElement(config.defaultStyleClose, close);
@@ -75,6 +83,20 @@ function setContents(event, notificationObj) {
if (notificationObj.color) {
container.style.backgroundColor = notificationObj.color;
+ let logo = notiDoc.getElementById('symphony-logo');
+
+ if (notificationObj.color.match(whiteColorRegExp)) {
+ logo.src = './assets/symphony-logo-black.png';
+ } else {
+ let title = notiDoc.getElementById('title');
+ let pod = notiDoc.getElementById('pod');
+ let message = notiDoc.getElementById('message');
+
+ message.style.color = '#ffffff';
+ title.style.color = '#ffffff';
+ pod.style.color = notificationObj.color;
+ logo.src = './assets/symphony-logo-white.png';
+ }
}
if (notificationObj.flash) {
diff --git a/js/notify/electron-notify.html b/js/notify/electron-notify.html
index 05243059..ec8c2c2b 100644
--- a/js/notify/electron-notify.html
+++ b/js/notify/electron-notify.html
@@ -2,11 +2,17 @@
-