diff --git a/config/Symphony.config b/config/Symphony.config
index 912f5acca..2cdeb2c52 100644
--- a/config/Symphony.config
+++ b/config/Symphony.config
@@ -4,6 +4,7 @@
"autoUpdateChannel": "latest",
"isAutoUpdateEnabled": true,
"autoUpdateCheckInterval": "30",
+ "enableSeamlessLogin": false,
"overrideUserAgent": false,
"minimizeOnClose" : "ENABLED",
"launchOnStartup" : "ENABLED",
diff --git a/installer/win/WixSharpInstaller/Symphony.cs b/installer/win/WixSharpInstaller/Symphony.cs
index 73da0b29c..511784a25 100644
--- a/installer/win/WixSharpInstaller/Symphony.cs
+++ b/installer/win/WixSharpInstaller/Symphony.cs
@@ -157,6 +157,7 @@ static public void Main(string[] args)
new PublicProperty("POINTER_LOCK", "true"),
new PublicProperty("USER_DATA_PATH", ""),
new PublicProperty("OVERRIDE_USER_AGENT", "false"),
+ new PublicProperty("ENABLE_SEAMLESS_LOGIN", "false"),
new PublicProperty("CHROME_FLAGS", ""),
new Property("MSIINSTALLPERUSER", "1"),
new Property("PROGRAMSFOLDER", System.Environment.ExpandEnvironmentVariables(@"%PROGRAMFILES%"))
@@ -185,7 +186,7 @@ static public void Main(string[] args)
new ElevatedManagedAction(CustomActions.UpdateConfig, Return.check, When.After, Step.InstallFiles, Condition.NOT_BeingRemoved )
{
// The UpdateConfig action needs the built-in property INSTALLDIR as well as most of the custom properties
- UsesProperties = "INSTALLDIR,POD_URL,CONTEXT_ORIGIN_URL,MINIMIZE_ON_CLOSE,ALWAYS_ON_TOP,AUTO_START,BRING_TO_FRONT,MEDIA,LOCATION,NOTIFICATIONS,MIDI_SYSEX,POINTER_LOCK,FULL_SCREEN,OPEN_EXTERNAL,CUSTOM_TITLE_BAR,DEV_TOOLS_ENABLED,AUTO_LAUNCH_PATH,USER_DATA_PATH,OVERRIDE_USER_AGENT,CHROME_FLAGS"
+ UsesProperties = "INSTALLDIR,POD_URL,CONTEXT_ORIGIN_URL,MINIMIZE_ON_CLOSE,ALWAYS_ON_TOP,AUTO_START,BRING_TO_FRONT,MEDIA,LOCATION,NOTIFICATIONS,MIDI_SYSEX,POINTER_LOCK,FULL_SCREEN,OPEN_EXTERNAL,CUSTOM_TITLE_BAR,DEV_TOOLS_ENABLED,AUTO_LAUNCH_PATH,USER_DATA_PATH,OVERRIDE_USER_AGENT,CHROME_FLAGS,ENABLE_SEAMLESS_LOGIN"
},
// CleanRegistry
@@ -359,7 +360,7 @@ public static ActionResult UpdateConfig(Session session)
data = ReplaceBooleanProperty(data, "fullscreen", session.Property("FULL_SCREEN"));
data = ReplaceBooleanProperty(data, "devToolsEnabled", session.Property("DEV_TOOLS_ENABLED"));
data = ReplaceBooleanProperty(data, "overrideUserAgent", session.Property("OVERRIDE_USER_AGENT"));
-
+ data = ReplaceBooleanProperty(data, "enableSeamlessLogin", session.Property("ENABLE_SEAMLESS_LOGIN"));
// Write the contents back to the file
System.IO.File.WriteAllText(filename, data);
}
diff --git a/installer/win/install_instructions_win.md b/installer/win/install_instructions_win.md
index 252e2e00d..ea331fbd1 100644
--- a/installer/win/install_instructions_win.md
+++ b/installer/win/install_instructions_win.md
@@ -529,3 +529,28 @@ Expected values:
msiexec /i Symphony.msi CHROME_FLAGS="--debug --debug2 --debug3"
+-------------------------------------------------------------------
+### ENABLE_SEAMLESS_LOGIN
+
+Expected values:
+
+* "true"
+ SDA will authenticate the user by relying on third-party browser
+* "false"
+ SDA will authenticate the user in SDA
+
+#### Example, install with user-agent override
+
+ msiexec /i Symphony.msi ENABLE_SEAMLESS_LOGIN="true"
+
+#### Example, install without user-agent override
+
+ msiexec /i Symphony.msi ENABLE_SEAMLESS_LOGIN="false"
+
+or
+
+ msiexec /i Symphony.msi
+
+
+
+-------------------------------------------------------------------
diff --git a/package-lock.json b/package-lock.json
index b55e37f34..d8fda9c21 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5399,15 +5399,21 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001265",
- "resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz",
- "integrity": "sha1-BhPJ5ski5CJ5Lm/O/fmjr+7k+MM=",
+ "version": "1.0.30001420",
+ "resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/caniuse-lite/-/caniuse-lite-1.0.30001420.tgz",
+ "integrity": "sha1-9i818FHgttJVMs83Z3bUHkW0fvY=",
"dev": true,
- "license": "CC-BY-4.0",
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- }
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ }
+ ],
+ "license": "CC-BY-4.0"
},
"node_modules/capture-exit": {
"version": "2.0.0",
@@ -24884,9 +24890,9 @@
"dev": true
},
"caniuse-lite": {
- "version": "1.0.30001265",
- "resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz",
- "integrity": "sha1-BhPJ5ski5CJ5Lm/O/fmjr+7k+MM=",
+ "version": "1.0.30001420",
+ "resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/caniuse-lite/-/caniuse-lite-1.0.30001420.tgz",
+ "integrity": "sha1-9i818FHgttJVMs83Z3bUHkW0fvY=",
"dev": true
},
"capture-exit": {
diff --git a/package.json b/package.json
index 601a9945f..b4436c3f0 100644
--- a/package.json
+++ b/package.json
@@ -239,4 +239,4 @@
"pre-commit": "pretty-quick --staged && npm run lint"
}
}
-}
+}
\ No newline at end of file
diff --git a/spec/__mocks__/electron.ts b/spec/__mocks__/electron.ts
index e06376d1b..9ded58fed 100644
--- a/spec/__mocks__/electron.ts
+++ b/spec/__mocks__/electron.ts
@@ -264,6 +264,7 @@ export const BrowserWindow = {
export const session = {
defaultSession: {
clearCache: jest.fn(),
+ cookies: jest.fn(),
},
};
diff --git a/spec/__snapshots__/welcome.spec.ts.snap b/spec/__snapshots__/welcome.spec.ts.snap
index 60886a49c..6f58e74d0 100644
--- a/spec/__snapshots__/welcome.spec.ts.snap
+++ b/spec/__snapshots__/welcome.spec.ts.snap
@@ -4,47 +4,151 @@ exports[`welcome should render correctly 1`] = `
-
-
-
-
- Pod URL
-
+
+
+
+ Welcome to the largest global community in financial services with over
+
+
+ half a million users
+
+
+ and more than
+
+
+ 1,000 institutions.
+
+
+
-
+
+ Log in with your pod URL
+
+
+
+
+ Pod URL
+
+
+
+
+
-
+
+
+ You’ll momentarily be redirected to your web browser.
+
+
`;
diff --git a/spec/protocolHandler.spec.ts b/spec/protocolHandler.spec.ts
index c4abfa611..39bc011a6 100644
--- a/spec/protocolHandler.spec.ts
+++ b/spec/protocolHandler.spec.ts
@@ -12,6 +12,14 @@ jest.mock('../src/common/utils', () => {
};
});
+jest.mock('../src/app/window-handler', () => {
+ return {
+ windowHandler: {
+ url: '',
+ },
+ };
+});
+
jest.mock('../src/common/env', () => {
return {
isWindowsOS: false,
@@ -34,6 +42,14 @@ jest.mock('../src/common/logger', () => {
};
});
+jest.mock('../src/app/config-handler', () => {
+ return {
+ config: {
+ getUserConfigFields: jest.fn(() => ''),
+ },
+ };
+});
+
describe('protocol handler', () => {
let protocolHandlerInstance;
diff --git a/spec/welcome.spec.ts b/spec/welcome.spec.ts
index 846562510..d37c5089b 100644
--- a/spec/welcome.spec.ts
+++ b/spec/welcome.spec.ts
@@ -9,6 +9,8 @@ describe('welcome', () => {
url: 'https://my.symphony.com',
message: '',
urlValid: true,
+ isPodConfigured: false,
+ isSeamlessLoginEnabled: true,
};
const onLabelEvent = 'on';
const removeListenerLabelEvent = 'removeListener';
@@ -94,7 +96,7 @@ describe('welcome', () => {
it('should set pod url', () => {
const spy = jest.spyOn(Welcome.prototype, 'setState');
- const setPodUrlSpy = jest.spyOn(Welcome.prototype, 'setPodUrl');
+ const setPodUrlSpy = jest.spyOn(Welcome.prototype, 'login');
const wrapper = shallow(React.createElement(Welcome));
ipcRenderer.send('welcome', welcomeMock);
@@ -104,4 +106,18 @@ describe('welcome', () => {
expect(setPodUrlSpy).toBeCalled();
expect(spy).toBeCalledWith(welcomeMock);
});
+
+ it('should not show pod url input field', () => {
+ const welcomeMock = {
+ url: 'https://my.symphony.com',
+ message: '',
+ urlValid: true,
+ isPodConfigured: true,
+ isSeamlessLoginEnabled: true,
+ };
+ const wrapper = shallow(React.createElement(Welcome));
+ ipcRenderer.send('welcome', welcomeMock);
+ const podUrlBox = `input.Welcome-main-container-podurl-box`;
+ expect(wrapper.find(podUrlBox).getElements()).toEqual([]);
+ });
});
diff --git a/src/app/app-menu.ts b/src/app/app-menu.ts
index 7fcf2d7fc..559015960 100644
--- a/src/app/app-menu.ts
+++ b/src/app/app-menu.ts
@@ -17,7 +17,7 @@ import {
MenuActionTypes,
} from './analytics-handler';
import { CloudConfigDataTypes, config, IConfig } from './config-handler';
-import { gpuRestartDialog, titleBarChangeDialog } from './dialog-handler';
+import { restartDialog, titleBarChangeDialog } from './dialog-handler';
import { exportCrashDumps, exportLogs } from './reports-handler';
import {
registerConsoleMessages,
@@ -84,6 +84,7 @@ const menuItemConfigFields = [
'isCustomTitleBar',
'devToolsEnabled',
'isAutoUpdateEnabled',
+ 'enableSeamlessLogin',
];
let {
@@ -95,6 +96,7 @@ let {
isCustomTitleBar,
devToolsEnabled,
isAutoUpdateEnabled,
+ enableSeamlessLogin,
} = config.getConfigFields(menuItemConfigFields) as IConfig;
let initialAnalyticsSent = false;
const CORP_URL = 'https://corporate.symphony.com';
@@ -240,6 +242,7 @@ export class AppMenu {
isCustomTitleBar = configData.isCustomTitleBar;
devToolsEnabled = configData.devToolsEnabled;
isAutoUpdateEnabled = configData.isAutoUpdateEnabled;
+ enableSeamlessLogin = configData.enableSeamlessLogin;
// fetch updated cloud config
this.cloudConfig = config.getFilteredCloudConfigFields(
this.menuItemConfigFields,
@@ -299,6 +302,7 @@ export class AppMenu {
windowHandler.createAboutAppWindow(windowName);
},
},
+ this.buildSeparator(),
{
click: (_item) => {
autoUpdate.checkUpdates(AutoUpdateTrigger.MANUAL);
@@ -513,6 +517,13 @@ export class AppMenu {
enabled:
!bringToFrontCC || bringToFrontCC === CloudConfigDataTypes.NOT_SET,
},
+ {
+ label: i18n.t('Third-party browser login')(),
+ checked: enableSeamlessLogin,
+ click: () => {
+ restartDialog({ enableSeamlessLogin: !enableSeamlessLogin });
+ },
+ },
this.buildSeparator(),
{
label:
@@ -664,7 +675,7 @@ export class AppMenu {
? i18n.t('Enable GPU')()
: i18n.t('Disable GPU')(),
click: () => {
- gpuRestartDialog(!this.disableGpu);
+ restartDialog({ disableGpu: !this.disableGpu });
},
},
{
diff --git a/src/app/config-handler.ts b/src/app/config-handler.ts
index 64ef14616..8b90fad6a 100644
--- a/src/app/config-handler.ts
+++ b/src/app/config-handler.ts
@@ -58,6 +58,7 @@ export interface IConfig {
installVariant?: string;
bootCount?: number;
startedAfterAutoUpdate?: boolean;
+ enableSeamlessLogin: boolean;
}
export interface IGlobalConfig {
diff --git a/src/app/dialog-handler.ts b/src/app/dialog-handler.ts
index 638526c6b..693b94d1c 100644
--- a/src/app/dialog-handler.ts
+++ b/src/app/dialog-handler.ts
@@ -200,10 +200,10 @@ export const titleBarChangeDialog = async (
};
/**
- * Displays a dialog to restart app upon changing gpu settings
- * @param disableGpu
+ * Displays a dialog to restart app upon changing config settings
+ * @param config
*/
-export const gpuRestartDialog = async (disableGpu: boolean) => {
+export const restartDialog = async (configFields: any) => {
const focusedWindow = BrowserWindow.getFocusedWindow();
if (!focusedWindow || !windowExists(focusedWindow)) {
return;
@@ -218,7 +218,7 @@ export const gpuRestartDialog = async (disableGpu: boolean) => {
cancelId: 1,
};
const { response } = await dialog.showMessageBox(focusedWindow, options);
- await config.updateUserConfig({ disableGpu });
+ await config.updateUserConfig(configFields);
if (response === 0) {
app.relaunch();
app.exit();
diff --git a/src/app/main-api-handler.ts b/src/app/main-api-handler.ts
index a46551df5..623372f5b 100644
--- a/src/app/main-api-handler.ts
+++ b/src/app/main-api-handler.ts
@@ -1,20 +1,23 @@
import {
- app,
BrowserWindow,
clipboard,
desktopCapturer,
dialog,
ipcMain,
+ shell,
systemPreferences,
} from 'electron';
+import fetch from 'electron-fetch';
import {
apiCmds,
apiName,
IApiArgs,
+ IAuthResponse,
INotificationData,
} from '../common/api-interface';
import { i18n, LocaleType } from '../common/i18n';
import { logger } from '../common/logger';
+import { whitelistHandler } from '../common/whitelist-handler';
import { activityDetection } from './activity-detection';
import { analytics } from './analytics-handler';
import appStateHandler from './app-state-handler';
@@ -46,6 +49,7 @@ import {
windowExists,
} from './window-utils';
+import { getCommandLineArgs } from '../common/utils';
import { autoUpdate, AutoUpdateTrigger } from './auto-update-handler';
// Swift search API
@@ -63,6 +67,9 @@ const broadcastMessage = (method, data) => {
mainEvents.publish(apiCmds.onSwiftSearchMessage, [method, data]);
};
+const getSeamlessLoginUrl = (pod: string) =>
+ `${pod}/login/sso/initsso?RelayState=${pod}/client-bff/device-login/index.html?callbackScheme=symphony&action=login`;
+const AUTH_STATUS_PATH = '/login/checkauth?type=user';
/**
* Handle API related ipc messages from renderers. Only messages from windows
* we have created are allowed.
@@ -350,10 +357,50 @@ ipcMain.on(
mainWebContents.focus();
}
break;
- case apiCmds.setPodUrl:
- await config.updateUserConfig({ url: arg.newPodUrl });
- app.relaunch();
- app.exit();
+ case apiCmds.seamlessLogin:
+ if (!arg.isPodConfigured) {
+ await config.updateUserConfig({ url: arg.newPodUrl });
+ }
+ const urlFromCmd = getCommandLineArgs(process.argv, '--url=', false);
+ const { url: userConfigURL } = config.getUserConfigFields(['url']);
+ const { url: globalConfigURL } = config.getGlobalConfigFields(['url']);
+ const podUrl = urlFromCmd
+ ? urlFromCmd.substr(6)
+ : userConfigURL
+ ? userConfigURL
+ : globalConfigURL;
+ const { subdomain, domain, tld } = whitelistHandler.parseDomain(podUrl);
+ const formattedPodUrl = `https://${subdomain}.${domain}${tld}`;
+ const loginUrl = getSeamlessLoginUrl(formattedPodUrl);
+ logger.info(
+ 'main-api-handler:',
+ 'check if sso is enabled for the pod',
+ formattedPodUrl,
+ );
+ const response = await fetch(`${formattedPodUrl}${AUTH_STATUS_PATH}`);
+ const authResponse = (await response.json()) as IAuthResponse;
+ logger.info('main-api-handler:', 'check auth response', authResponse);
+ if (
+ arg.isSeamlessLoginEnabled &&
+ authResponse.authenticationType === 'sso'
+ ) {
+ logger.info(
+ 'main-api-handler:',
+ 'seamless login is enabled - logging in',
+ loginUrl,
+ );
+ await shell.openExternal(loginUrl);
+ } else {
+ logger.info(
+ 'main-api-handler:',
+ 'seamless login is not enabled - loading main window with',
+ formattedPodUrl,
+ );
+ const mainWebContents = windowHandler.getMainWebContents();
+ if (mainWebContents && !mainWebContents.isDestroyed()) {
+ mainWebContents.loadURL(formattedPodUrl);
+ }
+ }
break;
case apiCmds.setBroadcastMessage:
if (swiftSearchInstance) {
diff --git a/src/app/protocol-handler.ts b/src/app/protocol-handler.ts
index 1dbf24569..1d908399d 100644
--- a/src/app/protocol-handler.ts
+++ b/src/app/protocol-handler.ts
@@ -1,9 +1,11 @@
-import { WebContents } from 'electron';
+import { session, WebContents } from 'electron';
import { apiName } from '../common/api-interface';
import { isMac } from '../common/env';
import { logger } from '../common/logger';
import { getCommandLineArgs } from '../common/utils';
+import { config } from './config-handler';
import { activate } from './window-actions';
+import { windowHandler } from './window-handler';
enum protocol {
SymphonyProtocol = 'symphony://',
@@ -45,7 +47,10 @@ class ProtocolHandler {
* @param url {String}
* @param isAppRunning {Boolean} - whether the application is running
*/
- public sendProtocol(url: string, isAppRunning: boolean = true): void {
+ public async sendProtocol(
+ url: string,
+ isAppRunning: boolean = true,
+ ): Promise {
if (url && url.length > 2083) {
logger.info(
`protocol-handler: protocol handler url length is greater than 2083, not performing any action!`,
@@ -55,6 +60,12 @@ class ProtocolHandler {
logger.info(
`protocol handler: processing protocol request for the url ${url}!`,
);
+ // Handle protocol for Seamless login
+ if (url?.includes('skey') && url?.includes('anticsrf')) {
+ await this.handleSeamlessLogin(url);
+ return;
+ }
+
if (!this.preloadWebContents || !isAppRunning) {
logger.info(
`protocol handler: app was started from the protocol request. Caching the URL ${url}!`,
@@ -62,6 +73,7 @@ class ProtocolHandler {
this.protocolUri = url;
return;
}
+
// This is needed for mac OS as it brings pop-outs to foreground
// (if it has been previously focused) instead of main window
if (isMac) {
@@ -95,6 +107,38 @@ class ProtocolHandler {
this.sendProtocol(protocolUriFromArgv, isAppAlreadyOpen);
}
}
+
+ /**
+ * Sets session cookies and navigates to the pod url
+ */
+ public async handleSeamlessLogin(protocolUri: string): Promise {
+ const { url } = config.getUserConfigFields(['url']);
+ if (protocolUri) {
+ const urlParams = new URLSearchParams(new URL(protocolUri).search);
+ const skeyValue = urlParams.get('skey');
+ const anticsrfValue = urlParams.get('anticsrf');
+ if (skeyValue) {
+ await session.defaultSession.cookies.set({
+ url,
+ name: 'skey',
+ value: skeyValue,
+ });
+ }
+ if (anticsrfValue) {
+ await session.defaultSession.cookies.set({
+ url,
+ name: 'anti-csrf-cookie',
+ value: anticsrfValue,
+ });
+ }
+ logger.info('protocol-handler: cookies has been set');
+ const mainWebContents = windowHandler.getMainWebContents();
+ if (mainWebContents && !mainWebContents?.isDestroyed() && url) {
+ logger.info('protocol-handler: redirecting main webContents', url);
+ mainWebContents?.loadURL(url);
+ }
+ }
+ }
}
const protocolHandler = new ProtocolHandler();
diff --git a/src/app/window-handler.ts b/src/app/window-handler.ts
index 61b8eed5a..2fd9bde0a 100644
--- a/src/app/window-handler.ts
+++ b/src/app/window-handler.ts
@@ -81,6 +81,9 @@ export enum ClientSwitchType {
CLIENT_2_0_DAILY = 'CLIENT_2_0_DAILY',
}
+export const DEFAULT_WELCOME_SCREEN_WIDTH: number = 542;
+export const DEFAULT_WELCOME_SCREEN_HEIGHT: number = 333;
+
const MAIN_WEB_CONTENTS_EVENTS = ['enter-full-screen', 'leave-full-screen'];
const SHORTCUT_KEY_THROTTLE = 1000; // 1sec
@@ -104,8 +107,6 @@ export interface ICustomBrowserView extends Electron.BrowserView {
// Default window width & height
export const DEFAULT_WIDTH: number = 900;
export const DEFAULT_HEIGHT: number = 900;
-export const DEFAULT_WELCOME_SCREEN_WIDTH: number = 542;
-export const DEFAULT_WELCOME_SCREEN_HEIGHT: number = 333;
export const TITLE_BAR_HEIGHT: number = 32;
export const IS_SAND_BOXED: boolean = true;
export const IS_NODE_INTEGRATION_ENABLED: boolean = false;
@@ -131,6 +132,7 @@ export class WindowHandler {
}
public mainView: ICustomBrowserView | null = null;
public titleBarView: ICustomBrowserView | null = null;
+ public welcomeScreenWindow: BrowserWindow | null = null;
public mainWebContents: WebContents | undefined;
public appMenu: AppMenu | null = null;
public isAutoReload: boolean = false;
@@ -145,7 +147,6 @@ export class WindowHandler {
public isLoggedIn: boolean = false;
public isAutoUpdating: boolean = false;
public screenShareIndicatorFrameUtil: string;
- private defaultPodUrl: string = 'https://[POD].symphony.com';
private contextIsolation: boolean = true;
private backgroundThrottling: boolean = false;
private windowOpts: ICustomBrowserWindowConstructorOpts =
@@ -158,7 +159,6 @@ export class WindowHandler {
private loadFailError: string | undefined;
private mainWindow: ICustomBrowserWindow | null = null;
private aboutAppWindow: Electron.BrowserWindow | null = null;
- private welcomeScreenWindow: Electron.BrowserWindow | null = null;
private screenPickerWindow: Electron.BrowserWindow | null = null;
private screenPickerPlaceholderWindow: Electron.BrowserWindow | null = null;
private screenSharingIndicatorWindow: Electron.BrowserWindow | null = null;
@@ -170,6 +170,9 @@ export class WindowHandler {
private readonly opts: Electron.BrowserViewConstructorOptions | undefined;
private hideOnCapture: boolean = false;
private currentWindow?: string = undefined;
+ private isPodConfigured: boolean = false;
+ private shouldShowWelcomeScreen: boolean = true;
+ private didShowWelcomeScreen: boolean = false;
constructor(opts?: Electron.BrowserViewConstructorOptions) {
this.opts = opts;
@@ -213,6 +216,7 @@ export class WindowHandler {
'customFlags',
'clientSwitch',
'enableRendererLogs',
+ 'enableSeamlessLogin',
]);
logger.info(
`window-handler: main windows initialized with following config data`,
@@ -238,6 +242,13 @@ export class WindowHandler {
this.isCustomTitleBar =
isWindowsOS &&
this.config.isCustomTitleBar === CloudConfigDataTypes.ENABLED;
+ // Get url to load from cmd line or from global config file
+ const urlFromCmd = getCommandLineArgs(process.argv, '--url=', false);
+ this.isPodConfigured = !config.isFirstTimeLaunch();
+ this.didShowWelcomeScreen = false;
+ this.shouldShowWelcomeScreen =
+ config.isFirstTimeLaunch() || this.config.enableSeamlessLogin;
+
this.windowOpts = {
...this.getWindowOpts(
{
@@ -285,20 +296,6 @@ export class WindowHandler {
);
logger.info(`window-handler: setting url ${this.url} from config file!`);
- // Get url to load from cmd line or from global config file
- const urlFromCmd = getCommandLineArgs(process.argv, '--url=', false);
-
- // Displays welcome screen instead of starting the main application
- if (
- config.isFirstTimeLaunch() &&
- this.globalConfig.url.indexOf('https://my.symphony.com') >= 0 &&
- urlFromCmd === null
- ) {
- this.url = this.defaultPodUrl;
- this.showWelcomeScreen();
- return;
- }
-
// set window opts with additional config
this.mainWindow = new BrowserWindow({
...this.windowOpts,
@@ -382,6 +379,20 @@ export class WindowHandler {
logger.info(`Loading main window with url ${this.url}`);
const userAgent = this.getUserAgent(this.mainWindow.webContents);
+ // Displays welcome screen instead of starting the main application
+ if (this.shouldShowWelcomeScreen) {
+ this.url = format({
+ pathname: require.resolve('../renderer/welcome.html'),
+ protocol: 'file',
+ query: {
+ componentName: 'welcome',
+ locale: i18n.getLocale(),
+ title: i18n.t('WelcomeText', 'Welcome')(),
+ },
+ slashes: true,
+ });
+ }
+
if (
this.config.isCustomTitleBar === CloudConfigDataTypes.ENABLED &&
isWindowsOS &&
@@ -513,9 +524,34 @@ export class WindowHandler {
await this.mainWebContents?.loadURL(url, { userAgent });
return;
}
+
logger.info('window-handler: did-finish-load, url: ' + this.url);
if (this.mainWebContents && !this.mainWebContents.isDestroyed()) {
+ // Load welcome screen
+ if (this.shouldShowWelcomeScreen && !this.didShowWelcomeScreen) {
+ const userConfigUrl =
+ this.userConfig.url &&
+ this.userConfig.url.indexOf('/login/sso/initsso') > -1
+ ? this.userConfig.url.slice(
+ 0,
+ this.userConfig.url.indexOf('/login/sso/initsso'),
+ )
+ : this.userConfig.url;
+ this.mainWebContents.send('page-load-welcome', {
+ locale: i18n.getLocale(),
+ resources: i18n.loadedResources,
+ });
+ this.mainWebContents.send('welcome', {
+ url: userConfigUrl,
+ message: '',
+ urlValid: !!userConfigUrl,
+ isPodConfigured: this.isPodConfigured,
+ isSeamlessLoginEnabled: this.config.enableSeamlessLogin,
+ });
+ this.didShowWelcomeScreen = true;
+ }
+
// Injects custom title bar and snack bar css into the webContents
await injectStyles(this.mainWebContents, this.isCustomTitleBar);
this.mainWebContents.send('page-load', {
@@ -846,13 +882,6 @@ export class WindowHandler {
return this.mainWindow;
}
- /**
- * Gets the welcome screen window
- */
- public getWelcomeScreenWindow(): BrowserWindow | null {
- return this.welcomeScreenWindow;
- }
-
/**
* Gets the main browser webContents
*/
diff --git a/src/app/window-utils.ts b/src/app/window-utils.ts
index bd742b943..45d56b658 100644
--- a/src/app/window-utils.ts
+++ b/src/app/window-utils.ts
@@ -384,8 +384,7 @@ export const updateLocale = async (locale: LocaleType): Promise => {
* Displays a popup menu
*/
export const showPopupMenu = (opts: Electron.PopupOptions): void => {
- const browserWindow =
- windowHandler.getMainWindow() || windowHandler.getWelcomeScreenWindow();
+ const browserWindow = windowHandler.getMainWindow();
if (
browserWindow &&
windowExists(browserWindow) &&
diff --git a/src/common/api-interface.ts b/src/common/api-interface.ts
index 621189ff8..dca9d88b0 100644
--- a/src/common/api-interface.ts
+++ b/src/common/api-interface.ts
@@ -58,7 +58,6 @@ export enum apiCmds {
isAeroGlassEnabled = 'is-aero-glass-enabled',
showScreenSharePermissionDialog = 'show-screen-share-permission-dialog',
getMediaAccessStatus = 'get-media-access-status',
- setPodUrl = 'set-pod-url',
setBroadcastMessage = 'set-broadcast-message',
handleSwiftSearchMessageEvents = 'handle-shift-search-message-events',
onSwiftSearchMessage = 'on-shift-search-message',
@@ -73,6 +72,7 @@ export enum apiCmds {
updateAndRestart = 'update-and-restart',
downloadUpdate = 'download-update',
checkForUpdates = 'check-for-updates',
+ seamlessLogin = 'seamless-login',
}
export enum apiName {
@@ -120,6 +120,9 @@ export interface IApiArgs {
requestId: number;
mediaStatus: IMediaPermission;
newPodUrl: string;
+ startUrl: string;
+ isPodConfigured: boolean;
+ isSeamlessLoginEnabled: boolean;
swiftSearchData: any;
types: string[];
thumbnailSize: Size;
@@ -288,3 +291,13 @@ export interface ICloud9Pipe {
write(data: Uint8Array): void;
close(): void;
}
+
+export type AuthType = 'password' | 'sso';
+
+export interface IAuthResponse {
+ status: string;
+ podVersion: string;
+ authenticationType: AuthType;
+ ssoDisabledForMobile: boolean;
+ keymanagerUrl: string;
+}
diff --git a/src/locale/en-US.json b/src/locale/en-US.json
index 6d9445fde..1392124f4 100644
--- a/src/locale/en-US.json
+++ b/src/locale/en-US.json
@@ -219,6 +219,13 @@
"Welcome": {
"Continue": "Continue",
"Enable Single Sign On": "Enable Single Sign On",
+ "Find your pod URL in your invitation email.": "Find your pod URL in your invitation email.",
+ "Welcome to the largest global community in financial services with over": "Welcome to the largest global community in financial services with over",
+ " half a million users": " half a million users",
+ " 1,000 institutions.": " 1,000 institutions.",
+ "Log in with your pod URL": "Log in with your pod URL",
+ "You’ll momentarily be redirected to your web browser.": "You’ll momentarily be redirected to your web browser.",
+ "log in": "log in",
"Please enter a valid url": "Please enter a valid url",
"Pod URL": "Pod URL",
"SSO": "SSO",
diff --git a/src/locale/en.json b/src/locale/en.json
index d9626f0f1..7e19a3d4e 100644
--- a/src/locale/en.json
+++ b/src/locale/en.json
@@ -219,6 +219,13 @@
"Welcome": {
"Continue": "Continue",
"Enable Single Sign On": "Enable Single Sign On",
+ "Find your pod URL in your invitation email.": "Find your pod URL in your invitation email.",
+ "Welcome to the largest global community in financial services with over": "Welcome to the largest global community in financial services with over",
+ " half a million users": " half a million users",
+ " 1,000 institutions.": " 1,000 institutions.",
+ "Log in with your pod URL": "Log in with your pod URL",
+ "You’ll momentarily be redirected to your web browser.": "You’ll momentarily be redirected to your web browser.",
+ "log in": "log in",
"Please enter a valid url": "Please enter a valid url",
"Pod URL": "Pod URL",
"SSO": "SSO",
diff --git a/src/renderer/assets/welcome-symphony-logo.svg b/src/renderer/assets/welcome-symphony-logo.svg
new file mode 100644
index 000000000..d5a4b2a2c
--- /dev/null
+++ b/src/renderer/assets/welcome-symphony-logo.svg
@@ -0,0 +1,11 @@
+
diff --git a/src/renderer/components/welcome.tsx b/src/renderer/components/welcome.tsx
index 72e577805..22c2cf628 100644
--- a/src/renderer/components/welcome.tsx
+++ b/src/renderer/components/welcome.tsx
@@ -7,21 +7,31 @@ interface IState {
url: string;
message: string;
urlValid: boolean;
+ isPodConfigured: boolean;
+ isSeamlessLoginEnabled: boolean;
+ isLoading: boolean;
}
const WELCOME_NAMESPACE = 'Welcome';
+const DEFAULT_MESSAGE = 'Find your pod URL in your invitation email.';
+const HEIGHT_WITH_POD_INPUT = '494px';
+const HEIGHT_WITHOUT_POD_INPUT = '376px';
+const DEFAULT_POD_URL = 'https://[POD].symphony.com';
export default class Welcome extends React.Component<{}, IState> {
private readonly eventHandlers = {
- onSetPodUrl: () => this.setPodUrl(),
+ onLogin: () => this.login(),
};
constructor(props) {
super(props);
this.state = {
- url: 'https://[POD].symphony.com',
+ url: DEFAULT_POD_URL,
message: '',
urlValid: false,
+ isPodConfigured: false,
+ isSeamlessLoginEnabled: true,
+ isLoading: false,
};
this.updateState = this.updateState.bind(this);
}
@@ -30,41 +40,76 @@ export default class Welcome extends React.Component<{}, IState> {
* Render the component
*/
public render(): JSX.Element {
- const { url, message, urlValid } = this.state;
+ const { url, message, isPodConfigured } = this.state;
return (
-
-
-
-
-
-
- {i18n.t('Pod URL', WELCOME_NAMESPACE)()}
-
-
-
-
-
+
+
+
+ {this.getWelcomeImage()}
-
-
+
+ {i18n.t(
+ 'Welcome to the largest global community in financial services with over',
+ WELCOME_NAMESPACE,
+ )()}
+
+
+ {i18n.t(' half a million users', WELCOME_NAMESPACE)()}
+
+ {i18n.t(' and more than', WELCOME_NAMESPACE)()}
+
+ {i18n.t(' 1,000 institutions.', WELCOME_NAMESPACE)()}
+
+
+ {!isPodConfigured && (
+
+
+
+ {i18n.t('Log in with your pod URL', WELCOME_NAMESPACE)()}
+
+
+
+ {i18n.t('Pod URL', WELCOME_NAMESPACE)()}
+
+
+
+
+
+
+ )}
+
+ {this.renderLoginButton()}
+
+
+
+ {i18n.t(
+ 'You’ll momentarily be redirected to your web browser.',
+ WELCOME_NAMESPACE,
+ )()}
+
+