Merge remote-tracking branch 'upstream/master' into skynet

# Conflicts:
#	js/windowMgr.js
#	package.json
This commit is contained in:
Keerthi Niranjan 2017-12-27 14:52:53 +05:30
commit 0c652be973
31 changed files with 577 additions and 88 deletions

32
PULL_REQUEST_TEMPLATE.md Normal file
View File

@ -0,0 +1,32 @@
## Description
A few sentences describing the overall goals of the pull request's commits. Describe the problem or feature in addition to a link to the [JIRA-ticket](https://perzoinc.atlassian.net/browse/JIRA-ticket)
## Approach
How does this change address the problem?
- #### Problem with the code:
- #### Fix:
## Learning
Describe the research stage. Put link to Confluence page if possible. Links to blog posts, patterns, libraries or addons used to solve this problem.
#### Blog Posts
- [Alice and Bob](https://en.wikipedia.org/wiki/Alice_and_Bob) Wikipage for the famous placeholder names in engineering literature.
## Related PRs
List related PRs against other branches:
branch | PR
------ | ------
other_pr_rc | [link]()
other_pr_dev | [link]()
## Open Questions if any and Todos
- [ ] Unit-Tests
- [ ] Documentation
- [ ] Automation-Tests
When solved, check the box and explain the answer.

View File

@ -2,6 +2,10 @@
# SymphonyElectron
## About:
SymphonyElectron is a desktop client of the Symphony Communication Platform built for macOS, Windows 10 and Windows 7.
## Project Goals:
Our goal is to improve the performance and development agility of Symphony's desktop wrapper and build a path to support other wrappers by:
@ -64,4 +68,6 @@ In order to achieve those goals Symphony is participating and working in close c
- Remote logging is enabled for local and production cases and are sent to the backend server via the remote objects
## Misc notes
If desiring to run against server without proper cert use cmd line option: --ignore-certificate-errors
- If desiring to run against server without proper cert use cmd line option: --ignore-certificate-errors
- To start additional instance with custom data directory (if you want seperate user) use cmd line options: --multiInstance --userDataPath=<path to data dir>
- if directory doesn't exist, it will be created

View File

@ -1,8 +1,9 @@
{
"url": "https://foundation-dev.symphony.com",
"minimizeOnClose" : false,
"minimizeOnClose" : true,
"launchOnStartup" : true,
"alwaysOnTop" : false,
"whitelistUrl": "*",
"notificationSettings": {
"position": "upper-right",
"display": ""
@ -11,5 +12,9 @@
"submitURL": "https://localhost:1127/post",
"companyName": "Symphony",
"uploadToServer": false
},
"customFlags": {
"authServerWhitelist": "",
"authNegotiateDelegateWhitelist": ""
}
}

View File

@ -18,6 +18,10 @@
<label for='image'>image url:</label>
<input type='text' id='image' value='https://avatars0.githubusercontent.com/u/13243259?v=4&s=460'/>
</p>
<p>
<label for='company'>company:</label>
<input type='text' id='company' value='Symphony'/>
</p>
<p>
<label for='flash'>flash:</label>
<input type='checkbox' id='flash'/>
@ -92,6 +96,7 @@
var shouldStick = document.getElementById('sticky').checked;
var color = document.getElementById('color').value;
var tag = document.getElementById('tag').value;
var company = document.getElementById('company').value;
num++;
@ -104,7 +109,8 @@
data: {
hello: 'hello word'
},
tag: tag
tag: tag,
company: company
});
notf.addEventListener('click', onclick);

View File

@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>17A405</string>
<string>17B48</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
@ -27,17 +27,17 @@
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>9A1004</string>
<string>9B55</string>
<key>DTPlatformVersion</key>
<string>GM</string>
<key>DTSDKBuild</key>
<string>17A360</string>
<string>17B41</string>
<key>DTSDKName</key>
<string>macosx10.13</string>
<key>DTXcode</key>
<string>0901</string>
<string>0910</string>
<key>DTXcodeBuild</key>
<string>9A1004</string>
<string>9B55</string>
<key>InstallerSectionTitle</key>
<string>Pod Settings</string>
<key>NSHumanReadableCopyright</key>

View File

@ -6,7 +6,7 @@
<dict>
<key>Resources/Base.lproj/MyInstallerPane.nib</key>
<data>
TF/AqkGdS25ttnHMS1l76ES81/w=
GZWN47BPj6b6mD6MnSVH/Qn0m3s=
</data>
<key>Resources/InstallerSections.plist</key>
<data>
@ -37,11 +37,11 @@
<dict>
<key>hash</key>
<data>
TF/AqkGdS25ttnHMS1l76ES81/w=
GZWN47BPj6b6mD6MnSVH/Qn0m3s=
</data>
<key>hash2</key>
<data>
gxXMI4SoTYE7jYkP5QJ7i804TUXR4x8LGSh99n9qers=
cJ/kr1HEozYL0YeZriWDtnOL1NEd22vHzH5WGp1T3hk=
</data>
</dict>
<key>Resources/InstallerSections.plist</key>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13196" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13529" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13196"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13529"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -59,7 +59,7 @@
<button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="XPe-yO-v9Y">
<rect key="frame" x="194" y="85" width="22" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" inset="2" id="uvu-EE-3sp">
<buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="uvu-EE-3sp">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>

View File

@ -31,7 +31,7 @@
}
// Now, validate the url against a url regex
NSString *regex = @"^((?:http:\/\/)|(?:https:\/\/))(www.)?((?:[a-zA-Z0-9]+\.[a-z]{3})|(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?::\d+)?))([\/a-zA-Z0-9\.]*)$";
NSString *regex = @"^(https:\/\/|http:\/\/)(www.)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}(:[0-9]{1,5})?(\/[a-zA-Z0-9-_.+!*'(),;/?:@=&$]*)?$";
NSPredicate *podUrlTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
if ([podUrlTest evaluateWithObject:podUrl]) {
return YES;
@ -48,23 +48,23 @@
NSString *podUrl = [_podUrlTextBox stringValue];
// By default, set autoLaunchOnStart to true
// By default, set autoLaunchOnStart and minimizeOnClose to true
NSString *autoLaunchOnStart = @"true";
NSString *minimizeOnClose = @"true";
// If the checkbox is changed, set the auto launch value accordingly
if ([_autoLaunchCheckBox state] == 0) {
autoLaunchOnStart = @"false";
}
// By default, set minimizeOnClose and alwaysOnTop to false
NSString *minimizeOnClose = @"false";
NSString *alwaysOnTop = @"false\n";
// If the checkbox is changed, set the minimize on close value accordingly
if ([_minimizeOnCloseCheckBox state] == 1) {
minimizeOnClose = @"true";
if ([_minimizeOnCloseCheckBox state] == 0) {
minimizeOnClose = @"false";
}
// By default, set alwaysOnTop to false
NSString *alwaysOnTop = @"false\n";
// If the checkbox is changed, set the always on top value accordingly
if ([_alwaysOnTopCheckBox state] == 1) {
alwaysOnTop = @"true\n";

View File

@ -4,24 +4,40 @@
tempFilePath='/tmp/sym_settings.txt'
installPath="$2"
configPath="/Symphony.app/Contents/config/Symphony.config"
newPath=$installPath$configPath
newPath=${installPath}${configPath}
## Get Symphony Settings from the temp file ##
pod_url=$(sed -n '1p' $tempFilePath);
pod_url=$(sed -n '1p' ${tempFilePath});
minimize_on_close=$(sed -n '2p' '/tmp/sym_settings.txt');
launch_on_startup=$(sed -n '3p' '/tmp/sym_settings.txt');
always_on_top=$(sed -n '4p' '/tmp/sym_settings.txt');
if [ "$pod_url" == "" ]; then
pod_url="https://corporate.symphony.com"
fi
if [ "$minimize_on_close" == "" ]; then
minimize_on_close=true;
fi
if [ "$launch_on_startup" == "" ]; then
launch_on_startup=true;
fi
if [ "$always_on_top" == "" ]; then
always_on_top=false;
fi
## Replace the default settings with the user selected settings ##
sed -i "" -E "s#\"url\" ?: ?\".*\"#\"url\"\: \"$pod_url\"#g" $newPath
sed -i "" -E "s#\"minimizeOnClose\" ?: ?([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])#\"minimizeOnClose\":\ $minimize_on_close#g" $newPath
sed -i "" -E "s#\"alwaysOnTop\" ?: ?([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])#\"alwaysOnTop\":\ $always_on_top#g" $newPath
sed -i "" -E "s#\"launchOnStartup\" ?: ?([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])#\"launchOnStartup\":\ $launch_on_startup#g" $newPath
sed -i "" -E "s#\"url\" ?: ?\".*\"#\"url\"\: \"$pod_url\"#g" ${newPath}
sed -i "" -E "s#\"minimizeOnClose\" ?: ?([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])#\"minimizeOnClose\":\ $minimize_on_close#g" ${newPath}
sed -i "" -E "s#\"alwaysOnTop\" ?: ?([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])#\"alwaysOnTop\":\ $always_on_top#g" ${newPath}
sed -i "" -E "s#\"launchOnStartup\" ?: ?([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])#\"launchOnStartup\":\ $launch_on_startup#g" ${newPath}
## Remove the temp settings file created ##
rm -f $tempFilePath
rm -f ${tempFilePath}
## For launching symphony with sandbox enabled, create a shell script that is used as the launch point for the app
EXEC_PATH=$installPath/Symphony.app/Contents/MacOS
exec $EXEC_PATH/Symphony --install $newPath $launch_on_startup
chmod 755 $EXEC_PATH/Symphony
EXEC_PATH=${installPath}/Symphony.app/Contents/MacOS
exec ${EXEC_PATH}/Symphony --install ${newPath} ${launch_on_startup}
chmod 755 ${EXEC_PATH}/Symphony

View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
sudo killall Symphony
sudo rm -rf /Applications/Symphony.app

View File

@ -450,6 +450,13 @@
<key>PATH_TYPE</key>
<integer>1</integer>
</dict>
<key>PREINSTALL_PATH</key>
<dict>
<key>PATH</key>
<string>preinstall.sh</string>
<key>PATH_TYPE</key>
<integer>1</integer>
</dict>
<key>RESOURCES</key>
<array/>
</dict>

View File

@ -24,6 +24,7 @@
<ROW Property="CTRLS" Value="2"/>
<ROW Property="DialogBitmap" Value="dialog" MultiBuildValue="DefaultBuild:Tabloid.jpg" Type="1" MsiKey="DialogBitmap"/>
<ROW Property="INVALID_POD_URL" Value="valid" Type="4"/>
<ROW Property="MINIMIZE_ON_CLOSE" Value="true"/>
<ROW Property="MINIMIZE_ON_CLOSE_LABEL" Value="false" Type="4"/>
<ROW Property="Manufacturer" Value="Symphony"/>
<ROW Property="POD_URL" Value="https://corporate.symphony.com" Type="4"/>
@ -552,7 +553,7 @@
<ROW Action="AI_TxtUpdaterRollback" Type="11521" Source="TxtUpdater.dll" Target="OnTxtUpdaterRollback" WithoutSeq="true"/>
<ROW Action="KillParagon" Type="1" Source="aicustact.dll" Target="StopProcess" Options="1" AdditionalSeq="AI_DATA_SETTER_2"/>
<ROW Action="KillRenderer" Type="1" Source="aicustact.dll" Target="StopProcess" Options="1" AdditionalSeq="AI_DATA_SETTER"/>
<ROW Action="PodUrlValidation" Type="37" Target="Script Text" TargetUnformatted="// First, check if the protocol is part of the url, if not, prepend it&#13;&#10;var prefix = &quot;https://&quot;;&#13;&#10;if (Session.Property(&quot;POD_URL&quot;).substr(0, prefix.length) !== prefix) {&#13;&#10;&#9;Session.Property(&quot;POD_URL&quot;) = prefix + Session.Property(&quot;POD_URL&quot;);&#13;&#10;}&#13;&#10;&#13;&#10;// Check if the entered pod url is valid&#13;&#10;var podUrlRE = /^((?:http:\/\/)|(?:https:\/\/))(www.)?((?:[a-zA-Z0-9]+\.[a-z]{3})|(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?::\d+)?))([\/a-zA-Z0-9\.]*)$/;&#13;&#10;var podUrlTest = podUrlRE.test(Session.Property(&quot;POD_URL&quot;));&#13;&#10;if (!podUrlTest) {&#13;&#10;&#9;Session.Property(&quot;INVALID_POD_URL&quot;) = &quot;invalid&quot;;&#13;&#10;} else {&#13;&#10;&#9;Session.Property(&quot;INVALID_POD_URL&quot;) = &quot;valid&quot;;&#13;&#10;}&#13;&#10;&#13;&#10;// By default, we set all the values to false and change based on conditions&#13;&#10;Session.Property(&quot;ALWAYS_ON_TOP_LABEL&quot;) = &quot;false&quot;; &#13;&#10;Session.Property(&quot;AUTO_START_LABEL&quot;) = &quot;false&quot;;&#13;&#10;Session.Property(&quot;MINIMIZE_ON_CLOSE_LABEL&quot;) = &quot;false&quot;;&#13;&#10;&#13;&#10;// If always on top is checked in the checkbox, set the label value to true&#13;&#10;if (Session.Property(&quot;ALWAYS_ON_TOP&quot;) &amp;&amp; Session.Property(&quot;ALWAYS_ON_TOP&quot;) === &quot;true&quot;)&#13;&#10;{&#13;&#10; Session.Property(&quot;ALWAYS_ON_TOP_LABEL&quot;) = &quot;true&quot;;&#13;&#10;}&#13;&#10;&#13;&#10;// If launch on startup is checked in the checkbox, set the label value to true&#13;&#10;if (Session.Property(&quot;MINIMIZE_ON_CLOSE&quot;) &amp;&amp; Session.Property(&quot;MINIMIZE_ON_CLOSE&quot;) === &quot;true&quot;)&#13;&#10;{&#13;&#10; Session.Property(&quot;MINIMIZE_ON_CLOSE_LABEL&quot;) = &quot;true&quot;;&#13;&#10;}&#13;&#10;&#13;&#10;// If minimise on close is checked in the checkbox, set the label value to true&#13;&#10;if (Session.Property(&quot;AUTO_START&quot;) &amp;&amp; Session.Property(&quot;AUTO_START&quot;) === &quot;true&quot;)&#13;&#10;{&#13;&#10; Session.Property(&quot;AUTO_START_LABEL&quot;) = &quot;true&quot;;&#13;&#10;}" WithoutSeq="true"/>
<ROW Action="PodUrlValidation" Type="37" Target="Script Text" TargetUnformatted="// First, check if the protocol is part of the url, if not, prepend it&#13;&#10;var prefix1 = &quot;https://&quot;;&#13;&#10;var prefix2 = &quot;http://&quot;;&#13;&#10;if (Session.Property(&quot;POD_URL&quot;).substr(0, prefix1.length) !== prefix1 &amp;&amp; Session.Property(&quot;POD_URL&quot;).substr(0, prefix2.length) !== prefix2) {&#13;&#10; Session.Property(&quot;POD_URL&quot;) = prefix1 + Session.Property(&quot;POD_URL&quot;);&#13;&#10;}&#13;&#10;&#13;&#10;// Check if the entered pod url is valid&#13;&#10;var podUrlRE = /^(https:\/\/|http:\/\/)(www.)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}(:[0-9]{1,5})?(\/[a-zA-Z0-9-_.+!*&apos;(),;/?:@=&amp;$]*)?$/;&#13;&#10;var podUrlTest = podUrlRE.test(Session.Property(&quot;POD_URL&quot;));&#13;&#10;if (!podUrlTest) {&#13;&#10;&#9;Session.Property(&quot;INVALID_POD_URL&quot;) = &quot;invalid&quot;;&#13;&#10;} else {&#13;&#10;&#9;Session.Property(&quot;INVALID_POD_URL&quot;) = &quot;valid&quot;;&#13;&#10;}&#13;&#10;&#13;&#10;// By default, we set all the values to false and change based on conditions&#13;&#10;Session.Property(&quot;ALWAYS_ON_TOP_LABEL&quot;) = &quot;false&quot;; &#13;&#10;Session.Property(&quot;AUTO_START_LABEL&quot;) = &quot;false&quot;;&#13;&#10;Session.Property(&quot;MINIMIZE_ON_CLOSE_LABEL&quot;) = &quot;false&quot;;&#13;&#10;&#13;&#10;// If always on top is checked in the checkbox, set the label value to true&#13;&#10;if (Session.Property(&quot;ALWAYS_ON_TOP&quot;) &amp;&amp; Session.Property(&quot;ALWAYS_ON_TOP&quot;) === &quot;true&quot;)&#13;&#10;{&#13;&#10; Session.Property(&quot;ALWAYS_ON_TOP_LABEL&quot;) = &quot;true&quot;;&#13;&#10;}&#13;&#10;&#13;&#10;// If launch on startup is checked in the checkbox, set the label value to true&#13;&#10;if (Session.Property(&quot;MINIMIZE_ON_CLOSE&quot;) &amp;&amp; Session.Property(&quot;MINIMIZE_ON_CLOSE&quot;) === &quot;true&quot;)&#13;&#10;{&#13;&#10; Session.Property(&quot;MINIMIZE_ON_CLOSE_LABEL&quot;) = &quot;true&quot;;&#13;&#10;}&#13;&#10;&#13;&#10;// If minimise on close is checked in the checkbox, set the label value to true&#13;&#10;if (Session.Property(&quot;AUTO_START&quot;) &amp;&amp; Session.Property(&quot;AUTO_START&quot;) === &quot;true&quot;)&#13;&#10;{&#13;&#10; Session.Property(&quot;AUTO_START_LABEL&quot;) = &quot;true&quot;;&#13;&#10;}" WithoutSeq="true"/>
<ROW Action="SET_APPDIR" Type="307" Source="APPDIR" Target="[ProgramFilesFolder][Manufacturer]\[ProductName]" MultiBuildTarget="DefaultBuild:[AI_UserProgramFiles][Manufacturer]\[ProductName]"/>
<ROW Action="SET_SHORTCUTDIR" Type="307" Source="SHORTCUTDIR" Target="[ProgramMenuFolder][ProductName]"/>
<ROW Action="SET_TARGETDIR_TO_APPDIR" Type="51" Source="TARGETDIR" Target="[APPDIR]"/>

View File

@ -26,6 +26,10 @@
font-size: .9em;
}
.credentials-error {
color: red;
}
form {
padding-top: 15px;
}
@ -60,6 +64,7 @@
<div class="container">
<span>Please provide your login credentials for:</span>
<span id="hostname" class="hostname">hostname</span>
<span id="credentialsError" class="credentials-error">Invalid user name/password</span>
<form id="basicAuth" name="Basic Auth" action="Login">
<table class="form">
<tbody>

View File

@ -7,6 +7,7 @@ const path = require('path');
const fs = require('fs');
const log = require('../log.js');
const logLevels = require('../enums/logLevels.js');
const { isMac } = require('../utils/misc');
let basicAuthWindow;
@ -14,7 +15,7 @@ const local = {};
let windowConfig = {
width: 360,
height: 270,
height: isMac ? 270 : 295,
show: false,
modal: true,
autoHideMenuBar: true,
@ -45,14 +46,20 @@ function getTemplatePath() {
* 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 {boolean} isValidCredentials - false if invalid username or password
* @param {Function} clearSettings
* @param {Function} callback
*/
function openBasicAuthWindow(windowName, hostname, callback) {
function openBasicAuthWindow(windowName, hostname, isValidCredentials, clearSettings, callback) {
// Register callback function
if (typeof callback === 'function') {
local.authCallback = callback;
}
// Register close function
if (typeof clearSettings === 'function') {
local.clearSettings = clearSettings;
}
// This prevents creating multiple instances of the
// basic auth window
@ -89,6 +96,7 @@ function openBasicAuthWindow(windowName, hostname, callback) {
basicAuthWindow.webContents.on('did-finish-load', () => {
basicAuthWindow.webContents.send('hostname', hostname);
basicAuthWindow.webContents.send('isValidCredentials', isValidCredentials);
});
basicAuthWindow.on('close', () => {
@ -103,14 +111,12 @@ function openBasicAuthWindow(windowName, hostname, callback) {
ipc.on('login', (event, args) => {
if (typeof args === 'object' && typeof local.authCallback === 'function') {
local.authCallback(args.username, args.password);
basicAuthWindow.close();
closeAuthWindow(false);
}
});
ipc.on('close-basic-auth', () => {
if (basicAuthWindow) {
basicAuthWindow.close();
}
closeAuthWindow(true);
});
/**
@ -120,6 +126,19 @@ function destroyWindow() {
basicAuthWindow = null;
}
/**
* Method to close the auth window
* @param {boolean} clearSettings - Whether to clear the auth settings
*/
function closeAuthWindow(clearSettings) {
if (clearSettings && typeof local.clearSettings === 'function') {
local.clearSettings();
}
if (basicAuthWindow) {
basicAuthWindow.close();
}
}
module.exports = {
openBasicAuthWindow: openBasicAuthWindow

View File

@ -52,4 +52,15 @@ ipc.on('hostname', (event, host) => {
if (hostname){
hostname.innerHTML = host || 'unknown';
}
});
/**
* Triggered if user credentials are invalid
*/
ipc.on('isValidCredentials', (event, isValidCredentials) => {
let credentialsError = document.getElementById('credentialsError');
if (credentialsError){
credentialsError.style.display = isValidCredentials ? 'none' : 'block'
}
});

View File

@ -382,6 +382,34 @@ function clearCachedConfigs() {
globalConfig = null;
}
function readConfigFileSync() {
let configPath;
let globalConfigFileName = path.join('config', configFileName);
if (isDevEnv) {
// for dev env, get config file from asar
configPath = path.join(app.getAppPath(), globalConfigFileName);
} else {
// for non-dev, config file is placed by installer relative to exe.
// this is so the config can be easily be changed post install.
let execPath = path.dirname(app.getPath('exe'));
// for mac exec is stored in subdir, for linux/windows config
// dir is in the same location.
configPath = path.join(execPath, isMac ? '..' : '', globalConfigFileName);
}
let data = fs.readFileSync(configPath);
try {
return JSON.parse(data);
} catch (err) {
console.log("parsing config failed", err);
}
return null;
}
module.exports = {
configFileName,
@ -395,6 +423,11 @@ module.exports = {
// items below here are only exported for testing, do NOT use!
saveUserConfig,
clearCachedConfigs
clearCachedConfigs,
readConfigFileSync,
// use only if you specifically need to read global config fields
getGlobalConfigField,
};

View File

@ -12,7 +12,8 @@
// renderer process, this will have to do. See github issue posted here to
// electron: https://github.com/electron/electron/issues/9312
const { ipcRenderer } = require('electron');
const { ipcRenderer, remote } = require('electron');
const { isWindowsOS } = require('../utils/misc');
let nextId = 0;
let includes = [].includes;
@ -52,6 +53,17 @@ function getSources(options, callback) {
};
}
if (isWindowsOS) {
/**
* Sets the captureWindow to false if Desktop composition
* is disabled otherwise true
*
* Setting captureWindow to false returns only screen sources
* @type {boolean}
*/
captureWindow = remote.systemPreferences.isAeroGlassEnabled();
}
id = getNextId();
ipcRenderer.send('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', captureWindow, captureScreen, updatedOptions.thumbnailSize, id);

View File

@ -3,6 +3,8 @@
const electron = require('electron');
const basicAuth = require('../basicAuth');
let currentAuthURL;
let tries = 0;
/**
* Having a proxy or hosts that requires authentication will allow user to
@ -12,14 +14,33 @@ electron.app.on('login', (event, webContents, request, authInfo, callback) => {
event.preventDefault();
// This check is to determine whether the request is for the same
// host if so then increase the login tries from which we can
// display invalid credentials
if (currentAuthURL !== request.url) {
currentAuthURL = request.url;
} else {
tries++
}
// name of the host to display
let hostname = authInfo.host || authInfo.realm;
let browserWin = electron.BrowserWindow.fromWebContents(webContents);
let windowName = browserWin.winName || '';
/**
* Method that resets currentAuthURL and tries
* if user closes the auth window
*/
function clearSettings() {
callback();
currentAuthURL = '';
tries = 0;
}
/**
* Opens an electron modal window in which
* user can enter credentials fot the host
*/
basicAuth.openBasicAuthWindow(windowName, hostname, callback);
basicAuth.openBasicAuthWindow(windowName, hostname, tries === 0, clearSettings, callback);
});

View File

@ -11,7 +11,7 @@ const AutoLaunch = require('auto-launch');
const urlParser = require('url');
// Local Dependencies
const {getConfigField, updateUserConfigWin, updateUserConfigMac} = require('./config.js');
const {getConfigField, updateUserConfigWin, updateUserConfigMac, readConfigFileSync} = require('./config.js');
const {setCheckboxValues} = require('./menus/menuTemplate.js');
const { isMac, isDevEnv } = require('./utils/misc.js');
const protocolHandler = require('./protocolHandler');
@ -81,8 +81,10 @@ const shouldQuit = app.makeSingleInstance((argv) => {
processProtocolAction(argv);
});
// quit if another instance is already running, ignore for dev env
if (!isDevEnv && shouldQuit) {
let allowMultiInstance = getCmdLineArg(process.argv, '--multiInstance', true) || isDevEnv;
// quit if another instance is already running, ignore for dev env or if app was started with multiInstance flag
if (!allowMultiInstance && shouldQuit) {
app.quit();
}
@ -103,6 +105,39 @@ if (isMac) {
});
}
/**
* Sets chrome authentication flags in electron
*/
function setChromeFlags() {
log.send(logLevels.INFO, 'checking if we need to set custom chrome flags!');
// Read the config parameters synchronously
let config = readConfigFileSync();
// If we cannot find any config, just skip setting any flags
if (config && config !== null && config.customFlags) {
log.send(logLevels.INFO, 'Chrome flags config found!');
// If we cannot find the authServerWhitelist config, move on
if (config.customFlags.authServerWhitelist && config.customFlags.authServerWhitelist !== "") {
log.send(logLevels.INFO, 'Setting auth server whitelist flag');
app.commandLine.appendSwitch('auth-server-whitelist', config.customFlags.authServerWhitelist);
}
// If we cannot find the authNegotiateDelegateWhitelist config, move on
if (config.customFlags.authNegotiateDelegateWhitelist && config.customFlags.authNegotiateDelegateWhitelist !== "") {
log.send(logLevels.INFO, 'Setting auth negotiate delegate whitelist flag');
app.commandLine.appendSwitch('auth-negotiate-delegate-whitelist', config.customFlags.authNegotiateDelegateWhitelist);
}
}
}
// Set the chrome flags
setChromeFlags();
/**
* This method will be called when Electron has finished
* initialization and is ready to create browser windows.
@ -176,6 +211,12 @@ function setupThenOpenMainWindow() {
// allows installer to launch app and set appropriate global / user config params.
let hasInstallFlag = getCmdLineArg(process.argv, '--install', true);
let perUserInstall = getCmdLineArg(process.argv, '--peruser', true);
let customDataArg = getCmdLineArg(process.argv, '--userDataPath=', false);
if (customDataArg && customDataArg.split('=').length > 1) {
let customDataFolder = customDataArg.split('=')[1];
app.setPath('userData', customDataFolder);
}
if (!isMac && hasInstallFlag) {
getConfigField('launchOnStartup')
.then(setStartup)
@ -193,9 +234,8 @@ function setupThenOpenMainWindow() {
let launchOnStartup = process.argv[3];
// We wire this in via the post install script
// to get the config file path where the app is installed
let appGlobalConfigPath = process.argv[2];
setStartup(launchOnStartup)
.then(() => updateUserConfigMac(appGlobalConfigPath))
.then(updateUserConfigMac)
.then(app.quit)
.catch(app.quit);
return;
@ -214,18 +254,15 @@ function setupThenOpenMainWindow() {
* @returns {Promise}
*/
function setStartup(lStartup) {
return symphonyAutoLauncher.isEnabled()
.then(function(isEnabled) {
if (!isEnabled && lStartup) {
return symphonyAutoLauncher.enable();
}
if (isEnabled && !lStartup) {
return symphonyAutoLauncher.disable();
}
return true;
});
return new Promise((resolve) => {
let launchOnStartup = (lStartup === 'true');
if (launchOnStartup) {
symphonyAutoLauncher.enable();
return resolve();
}
symphonyAutoLauncher.disable();
return resolve();
});
}
/**

View File

@ -65,7 +65,23 @@ const template = [{
}
},
{
label: 'Open Crashes Directory',
label: 'Set Downloads Directory',
click() {
electron.dialog.showOpenDialog({
title: 'Select Downloads Directory',
buttonLabel: 'Select',
properties: ['openDirectory', 'createDirectory']
}, (filePaths) => {
if (!filePaths || !Array.isArray(filePaths) || filePaths.length < 1) {
return;
}
updateConfigField('downloadsDirectory', filePaths[0]);
eventEmitter.emit('setDownloadsDirectory', filePaths[0]);
});
}
},
{
label: 'Open Crashes Directory',
click() {
const crashesDirectory = electron.crashReporter.getCrashesDirectory() + '/completed';
electron.shell.showItemInFolder(crashesDirectory);

View File

@ -23,7 +23,7 @@ function setStyle(config) {
let image = notiDoc.getElementById('image');
let logo = notiDoc.getElementById('symphony-logo');
let title = notiDoc.getElementById('title');
let pod = notiDoc.getElementById('pod');
let company = notiDoc.getElementById('company');
let message = notiDoc.getElementById('message');
let close = notiDoc.getElementById('close');
@ -45,7 +45,7 @@ function setStyle(config) {
setStyleOnDomElement(config.defaultStyleTitle, title);
setStyleOnDomElement(config.defaultStylePod, pod);
setStyleOnDomElement(config.defaultStyleCompany, company);
setStyleOnDomElement(config.defaultStyleText, message);
@ -79,7 +79,13 @@ function setContents(event, notificationObj) {
let notiDoc = window.document;
// All the required DOM elements to update the content
let container = notiDoc.getElementById('container');
let titleDoc = notiDoc.getElementById('title');
let companyDoc = notiDoc.getElementById('company');
let messageDoc = notiDoc.getElementById('message');
let imageDoc = notiDoc.getElementById('image');
let closeButton = notiDoc.getElementById('close');
if (notificationObj.color) {
container.style.backgroundColor = notificationObj.color;
@ -88,13 +94,9 @@ function setContents(event, notificationObj) {
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;
messageDoc.style.color = '#ffffff';
titleDoc.style.color = '#ffffff';
companyDoc.style.color = notificationObj.color;
logo.src = './assets/symphony-logo-white.png';
}
}
@ -111,24 +113,26 @@ function setContents(event, notificationObj) {
}
// Title
let titleDoc = notiDoc.getElementById('title');
titleDoc.innerHTML = notificationObj.title || '';
// message
let messageDoc = notiDoc.getElementById('message');
messageDoc.innerHTML = notificationObj.text || '';
// Image
let imageDoc = notiDoc.getElementById('image');
if (notificationObj.image) {
imageDoc.src = notificationObj.image;
} else {
setStyleOnDomElement({ display: 'none'}, imageDoc);
}
const winId = notificationObj.windowId;
// Company
if (notificationObj.company) {
companyDoc.innerHTML = notificationObj.company
} else {
messageDoc.style.marginTop = '15px';
}
let closeButton = notiDoc.getElementById('close');
const winId = notificationObj.windowId;
// note: use onclick because we only want one handler, for case
// when content gets overwritten by notf with same tag

View File

@ -7,7 +7,7 @@
</div>
<div id="header">
<span id="title"></span>
<span id="pod"></span>
<span id="company"></span>
<span id="message"></span>
</div>
<div id="picture">

View File

@ -76,7 +76,7 @@ let config = {
defaultStyleHeader: {
width: 245,
minWidth: 230,
margin: "12px 10px"
margin: "10px 10px"
},
defaultStyleImage: {
height: 43,
@ -102,7 +102,7 @@ let config = {
webkitLineClamp: 1,
webkitBoxOrient: 'vertical',
},
defaultStylePod: {
defaultStyleCompany: {
fontFamily: 'sans-serif',
fontSize: 11,
color: '#adadad',
@ -116,7 +116,7 @@ let config = {
fontFamily: 'sans-serif',
fontSize: 12,
color: '#4a4a4a',
marginTop: 12,
marginTop: 5,
overflow: 'hidden',
display: '-webkit-box',
webkitLineClamp: 1,

View File

@ -41,6 +41,7 @@ class Notify {
color: options.color,
tag: options.tag,
sticky: options.sticky || false,
company: options.company,
onShowFunc: onShow.bind(this),
onClickFunc: onClick.bind(this),
onCloseFunc: onClose.bind(this),

View File

@ -4,11 +4,13 @@ const isDevEnv = process.env.ELECTRON_DEV ?
process.env.ELECTRON_DEV.trim().toLowerCase() === 'true' : false;
const isMac = (process.platform === 'darwin');
const isWindowsOS = (process.platform === 'win32');
const isNodeEnv = !!process.env.NODE_ENV;
module.exports = {
isDevEnv: isDevEnv,
isMac: isMac,
isWindowsOS: isWindowsOS,
isNodeEnv: isNodeEnv
};

View File

@ -0,0 +1,120 @@
'use strict';
const { getGlobalConfigField } = require('./../config.js');
const parseDomain = require('parse-domain');
const isEqual = require('lodash.isequal');
/**
* Loops through the list of whitelist urls
* @param url {String} - url the electron is navigated to
* @returns {Promise}
*/
function isWhitelisted(url) {
return new Promise((resolve, reject) => {
getGlobalConfigField('whitelistUrl').then((whitelist) => {
if (checkWhitelist(url, whitelist)) {
return resolve();
}
return reject(new Error('URL does not match with the whitelist'));
}).catch((err) => {
reject(err);
});
});
}
/**
* Method that compares url against a list of whitelist
* returns true if hostName or domain present in the whitelist
* @param url {String} - url the electron is navigated to
* @param whitelist {String} - coma separated whitelists
* @returns {boolean}
*/
function checkWhitelist(url, whitelist) {
let whitelistArray = whitelist.split(',');
const parsedUrl = parseDomain(url);
if (!parsedUrl) {
return false;
}
if (!whitelist) {
return false;
}
if (!whitelistArray.length || whitelistArray.indexOf('*') !== -1) {
return true;
}
return whitelistArray.some((whitelistHost) => {
let parsedWhitelist = parseDomain(whitelistHost);
if (!parsedWhitelist) {
return false;
}
return matchDomains(parsedUrl, parsedWhitelist);
});
}
/**
* Matches the respective hostName
* @param parsedUrl {Object} - parsed url
* @param parsedWhitelist {Object} - parsed whitelist
*
* example:
* matchDomain({ subdomain: www, domain: example, tld: com }, { subdomain: app, domain: example, tld: com })
*
* @returns {*}
*/
function matchDomains(parsedUrl, parsedWhitelist) {
if (isEqual(parsedUrl, parsedWhitelist)) {
return true;
}
const hostNameFromUrl = parsedUrl.domain + parsedUrl.tld;
const hostNameFromWhitelist = parsedWhitelist.domain + parsedWhitelist.tld;
if (!parsedWhitelist.subdomain) {
return hostNameFromUrl === hostNameFromWhitelist
}
return hostNameFromUrl === hostNameFromWhitelist && matchSubDomains(parsedUrl.subdomain, parsedWhitelist.subdomain);
}
/**
* Matches the last occurrence in the sub-domain
* @param subDomainUrl {String} - sub-domain from url
* @param subDomainWhitelist {String} - sub-domain from whitelist
*
* example: matchSubDomains('www', 'app')
*
* @returns {boolean}
*/
function matchSubDomains(subDomainUrl, subDomainWhitelist) {
if (subDomainUrl === subDomainWhitelist) {
return true;
}
const subDomainUrlArray = subDomainUrl.split('.');
const lastCharSubDomainUrl = subDomainUrlArray[subDomainUrlArray.length - 1];
const subDomainWhitelistArray = subDomainWhitelist.split('.');
const lastCharWhitelist = subDomainWhitelistArray[subDomainWhitelistArray.length - 1];
return lastCharSubDomainUrl === lastCharWhitelist;
}
module.exports = {
isWhitelisted,
// items below here are only exported for testing, do NOT use!
checkWhitelist
};

View File

@ -1,5 +1,6 @@
'use strict';
const fs = require('fs');
const electron = require('electron');
const app = electron.app;
const crashReporter = electron.crashReporter;
@ -21,6 +22,7 @@ const throttle = require('./utils/throttle.js');
const { getConfigField, updateConfigField } = require('./config.js');
const { isMac, isNodeEnv } = require('./utils/misc');
const { deleteIndexFolder } = require('./search/search.js');
const { isWhitelisted } = require('./utils/whitelistHandler');
// show dialog when certificate errors occur
require('./dialogs/showCertError.js');
@ -37,6 +39,7 @@ let alwaysOnTop = false;
let position = 'lower-right';
let display;
let sandboxed = false;
let downloadsDirectory;
// note: this file is built using browserify in prebuild step.
const preloadMainScript = path.join(__dirname, 'preload/_preloadMain.js');
@ -128,8 +131,8 @@ function doCreateMainWindow(initialUrl, initialBounds) {
newWinOpts.width = bounds.width;
newWinOpts.height = bounds.height;
} else {
newWinOpts.width = 1024;
newWinOpts.height = 768;
newWinOpts.width = 900;
newWinOpts.height = 900;
}
// will center on screen if values not provided
@ -238,11 +241,36 @@ function doCreateMainWindow(initialUrl, initialBounds) {
mainWindow.on('closed', destroyAllWindows);
// if an user has set a custom downloads directory,
// we get that data from the user config file
getConfigField('downloadsDirectory')
.then((value) => {
downloadsDirectory = value;
// if the directory has been deleted, try creating it.
if (!fs.existsSync(downloadsDirectory)) {
const directoryCreated = fs.mkdirSync(downloadsDirectory);
// If the directory creation failed, we use the default downloads directory
if (!directoryCreated) {
downloadsDirectory = null;
}
}
})
.catch((error) => {
log.send(logLevels.ERROR, 'Could not find the downloads directory config -> ' + error);
});
// Manage File Downloads
mainWindow.webContents.session.on('will-download', (event, item, webContents) => {
// When download is in progress, send necessary data to indicate the same
webContents.send('downloadProgress');
// if the user has set a custom downloads directory, save file to that directory
// if otherwise, we save it to the operating system's default downloads directory
if (downloadsDirectory) {
item.setSavePath(downloadsDirectory + "/" + item.getFilename());
}
// Send file path when download is complete
item.once('done', (e, state) => {
if (state === 'completed') {
@ -257,11 +285,6 @@ function doCreateMainWindow(initialUrl, initialBounds) {
});
});
// To delete the user index data folder on navigation
mainWindow.webContents.on('will-navigate', () => {
deleteIndexFolder();
});
getConfigField('url')
.then(initializeCrashReporter)
.catch(app.quit);
@ -353,6 +376,12 @@ function doCreateMainWindow(initialUrl, initialBounds) {
if (browserWin) {
log.send(logLevels.INFO, 'loaded pop-out window url: ' + newWinParsedUrl);
if (!isMac) {
// Removes the menu bar from the pop-out window
// setMenu is currently only supported on Windows and Linux
browserWin.setMenu(null);
}
getConfigField('url')
.then((podUrl) => {
getConfigField('crashReporter')
@ -435,6 +464,21 @@ function doCreateMainWindow(initialUrl, initialBounds) {
}
});
// whenever the main window is navigated for ex: window.location.href or url redirect
mainWindow.webContents.on('will-navigate', function(event, navigatedURL) {
deleteIndexFolder();
isWhitelisted(navigatedURL)
.catch(() => {
event.preventDefault();
electron.dialog.showMessageBox(mainWindow, {
type: 'warning',
buttons: [ 'Ok' ],
title: 'Not Allowed',
message: `Sorry, you are not allowed to access this website (${navigatedURL}), please contact your administrator for more details`,
});
});
});
}
/**
@ -590,6 +634,13 @@ function isAlwaysOnTop(boolean) {
browserWins.forEach(function (browser) {
browser.setAlwaysOnTop(boolean);
});
// An issue where changing the alwaysOnTop property
// focus the pop-out window
// Issue - Electron-209
if (mainWindow && mainWindow.winName) {
activate(mainWindow.winName);
}
}
}
@ -598,6 +649,11 @@ eventEmitter.on('isAlwaysOnTop', (boolean) => {
isAlwaysOnTop(boolean);
});
// set downloads directory
eventEmitter.on('setDownloadsDirectory', (newDirectory) => {
downloadsDirectory = newDirectory;
});
// node event emitter for notification settings
eventEmitter.on('notificationSettings', (notificationSettings) => {
position = notificationSettings.position;

View File

@ -1,8 +1,8 @@
{
"name": "Symphony",
"productName": "Symphony",
"version": "2.0.0",
"buildNumber": "",
"version": "2.2.0",
"buildNumber": "124",
"description": "Symphony desktop app (Foundation ODP)",
"author": "Symphony",
"main": "js/main.js",
@ -112,9 +112,11 @@
"ffi": "^2.2.0",
"filesize": "^3.5.10",
"keymirror": "0.1.1",
"lodash.difference": "^4.5.0",
"lodash.isequal": "^4.5.0",
"lodash.omit": "^4.5.0",
"lodash.pick": "^4.4.0",
"lodash.difference": "^4.5.0",
"parse-domain": "^2.0.0",
"randomstring": "^1.1.5",
"ref": "^1.3.4",
"shell-path": "^2.1.0",

View File

@ -0,0 +1,74 @@
const { checkWhitelist } = require('../../js/utils/whitelistHandler');
describe('validate url with whitelist', function() {
describe('checkWhitelist truth tests', function() {
it('should return true when the url is valid', function() {
const whitelist = 'www.symphony.com, app.symphony.com, my.symphony.com';
const url = 'https://my.symphony.com/';
return expect(checkWhitelist(url, whitelist)).toBeTruthy();
});
it('should return true when if hostName is defined', function() {
const whitelist = 'www.symphony.com, app.symphony.com, symphony.com';
const url = 'https://xyz.symphony.com/';
return expect(checkWhitelist(url, whitelist)).toBeTruthy();
});
it('should return true when the first occurrence of sub-domain is matched', function() {
const whitelist = 'www.symphony.com, app.symphony.com, my.symphony.com';
const url = 'https://xyz.my.symphony.com/';
return expect(checkWhitelist(url, whitelist)).toBeTruthy();
});
it('should return true when for any URL if whitelist has *', function() {
const whitelist = '*';
const url = 'https://www.example.com/';
return expect(checkWhitelist(url, whitelist)).toBeTruthy();
});
});
describe('checkWhitelist falsity tests', function () {
it('should return false when sub-domain does not match', function () {
const whitelist = 'www.symphony.com, app.symphony.com, my.symphony.com';
const url = 'https://xyz.symphony.com/';
return expect(checkWhitelist(url, whitelist)).toBeFalsy();
});
it('should return false when hostName does not match', function () {
const whitelist = 'www.symphony.com, app.symphony.com, my.symphony.com';
const url = 'https://my.example.com/';
return expect(checkWhitelist(url, whitelist)).toBeFalsy();
});
it('should return false when the URL is invalid', function () {
const whitelist = 'www.symphony.com, app.symphony.com, my.symphony.com';
const url = 'invalidUrl';
return expect(checkWhitelist(url, whitelist)).toBeFalsy();
});
it('should return false when the whitelist is invalid', function () {
const whitelist = 'invalidWhitelist';
const url = 'https://www.symphony.com';
return expect(checkWhitelist(url, whitelist)).toBeFalsy();
});
it('should return false if whitelist is empty', function() {
const whitelist = '';
const url = 'https://www.example.com/';
return expect(checkWhitelist(url, whitelist)).toBeFalsy();
});
});
});