SDA-4089 Browser login autoconnect (#1809)

* RTC-13931 Disable D3D11 by default

* SDA-4089 Browser login autolaunch

* SDA-4089 Browser login autolaunch

* SDA-4089 Browser login autolaunch

* SDA-4089 Browser login autolaunch

* SDA-4089 Browser login autolaunch

* SDA-4089 Browser login autolaunch

* SDA-4089 Browser login autolaunch

* cleanup
This commit is contained in:
Salah Benmoussati 2023-03-28 15:10:18 +02:00
parent 6f9239e577
commit 0af813ae97
23 changed files with 449 additions and 144 deletions

View File

@ -5,6 +5,7 @@
"isAutoUpdateEnabled": true,
"autoUpdateCheckInterval": "30",
"enableBrowserLogin": false,
"browserLoginAutoConnect": false,
"overrideUserAgent": false,
"minimizeOnClose" : "ENABLED",
"launchOnStartup" : "ENABLED",

View File

@ -18,6 +18,7 @@ always_on_top=$(sed -n '5p' ${settingsFilePath});
bring_to_front=$(sed -n '6p' ${settingsFilePath});
dev_tools_enabled=$(sed -n '7p' ${settingsFilePath});
enable_browser_login=$(sed -n '8p' ${settingsFilePath});
browser_login_autoconnect=$(sed -n '9p' ${settingsFilePath});
## If any of the above values turn out to be empty, set default values ##
if [ "$pod_url" = "" ]; then pod_url="https://my.symphony.com"; fi
@ -28,6 +29,8 @@ if [ "$always_on_top" = "" ] || [ "$always_on_top" = 'false' ]; then always_on_t
if [ "$bring_to_front" = "" ] || [ "$bring_to_front" = 'false' ]; then bring_to_front='DISABLED'; else bring_to_front='ENABLED'; fi
if [ "$dev_tools_enabled" = "" ]; then dev_tools_enabled=true; fi
if [ "$enable_browser_login" = "" ]; then enable_browser_login=false; fi
if [ "$browser_login_autoconnect" = "" ]; then browser_login_autoconnect=false; fi
pod_url_escaped=$(sed 's#[&/\]#\\&#g' <<<"$pod_url")
context_origin_url_escaped=$(sed 's#[&/\]#\\&#g' <<<"$context_origin_url")
@ -40,6 +43,7 @@ sed -i "" -E "s#\"launchOnStartup\" ?: ?\"([Ee][Nn][Aa][Bb][Ll][Ee][Dd]|[Dd][Ii]
sed -i "" -E "s#\"bringToFront\" ?: ?\"([Ee][Nn][Aa][Bb][Ll][Ee][Dd]|[Dd][Ii][Ss][Aa][Bb][Ll][Ee][Dd])\"#\"bringToFront\":\ \"$bring_to_front\"#g" "${newPath}"
sed -i "" -E "s#\"devToolsEnabled\" ?: ?([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])#\"devToolsEnabled\":\ $dev_tools_enabled#g" "${newPath}"
sed -i "" -E "s#\"enableBrowserLogin\" ?: ?([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])#\"enableBrowserLogin\":\ $enable_browser_login#g" "${newPath}"
sed -i "" -E "s#\"browserLoginAutoConnect\" ?: ?([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])#\"browserLoginAutoConnect\":\ $browser_login_autoconnect#g" "${newPath}"
## Get Symphony Permissions from the temp file ##
media=$(sed -n '1p' ${permissionsFilePath});

View File

@ -159,6 +159,7 @@ class Script
new PublicProperty("USER_DATA_PATH", ""),
new PublicProperty("OVERRIDE_USER_AGENT", "false"),
new PublicProperty("ENABLE_BROWSER_LOGIN", "false"),
new PublicProperty("BROWSER_LOGIN_AUTOCONNECT", "false"),
new PublicProperty("CHROME_FLAGS", ""),
new Property("MSIINSTALLPERUSER", "1"),
new Property("PROGRAMSFOLDER", System.Environment.ExpandEnvironmentVariables(@"%PROGRAMFILES%"))
@ -187,7 +188,7 @@ class Script
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,ENABLE_BROWSER_LOGIN"
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_BROWSER_LOGIN,BROWSER_LOGIN_AUTOCONNECT"
},
// CleanRegistry
@ -362,6 +363,7 @@ public class CustomActions
data = ReplaceBooleanProperty(data, "devToolsEnabled", session.Property("DEV_TOOLS_ENABLED"));
data = ReplaceBooleanProperty(data, "overrideUserAgent", session.Property("OVERRIDE_USER_AGENT"));
data = ReplaceBooleanProperty(data, "enableBrowserLogin", session.Property("ENABLE_BROWSER_LOGIN"));
data = ReplaceBooleanProperty(data, "browserLoginAutoConnect", session.Property("BROWSER_LOGIN_AUTOCONNECT"));
// Write the contents back to the file
System.IO.File.WriteAllText(filename, data);
}

View File

@ -535,15 +535,15 @@ Expected values:
Expected values:
* "true"
SDA will authenticate the user by relying on third-party browser
SDA will authenticate the user by relying on default browser
* "false"
SDA will authenticate the user in SDA
#### Example, install with user-agent override
#### Example, install with browser login enabled
msiexec /i Symphony.msi ENABLE_BROWSER_LOGIN="true"
#### Example, install without user-agent override
#### Example, install without browser login enabled
msiexec /i Symphony.msi ENABLE_BROWSER_LOGIN="false"
@ -554,3 +554,25 @@ or
-------------------------------------------------------------------
### BROWSER_LOGIN_AUTOCONNECT
Acts in combination with ENABLE_BROWSER_LOGIN, if ENABLE_BROWSER_LOGIN is set to true.
Expected values:
* "true"
SDA will automatically authenticate the user by relying on default browser
* "false"
User will need to click on Login button to start browser login flow.
#### Example, install with browser login autoconnect enabled
msiexec /i Symphony.msi BROWSER_LOGIN_AUTOCONNECT="true"
#### Example, install with browser login autoconnect disabled
msiexec /i Symphony.msi BROWSER_LOGIN_AUTOCONNECT="false"
or
msiexec /i Symphony.msi

View File

@ -99,35 +99,60 @@ exports[`welcome should render correctly 1`] = `
1,000 institutions.
</span>
</div>
<div>
<div
className="Welcome-login-text"
>
<span>
Log in with your pod URL
</span>
<div
className="Welcome-login-text"
>
<span>
Log in with your pod URL
</span>
</div>
<div
className="Welcome-input-container"
>
<span>
Pod URL
</span>
<div>
<input
className="Welcome-main-container-podurl-box"
data-testid="Welcome-main-container-podurl-box"
disabled={false}
onChange={[Function]}
tabIndex={0}
type="url"
value="https://[POD].symphony.com"
/>
<label
className="Welcome-input-message"
>
Find your pod URL in your invitation email.
</label>
</div>
<div
className="Welcome-input-container"
</div>
<div
className="Welcome-auto-connect-wrapper"
>
<label
className="switch"
>
<span>
Pod URL
<input
checked={false}
disabled={false}
onChange={[Function]}
type="checkbox"
/>
<span
className="slider round"
/>
</label>
<div
className="auto-connect-labels"
>
<span
className="auto-connect-label"
>
Automatically redirect to your web browser on launch
</span>
<div>
<input
className="Welcome-main-container-podurl-box"
data-testid="Welcome-main-container-podurl-box"
onChange={[Function]}
tabIndex={0}
type="url"
value="https://[POD].symphony.com"
/>
<label
className="Welcome-input-message"
>
Find your pod URL in your invitation email.
</label>
</div>
</div>
</div>
<button

View File

@ -10,7 +10,8 @@ describe('welcome', () => {
message: '',
urlValid: true,
isPodConfigured: false,
isSeamlessLoginEnabled: true,
isBrowserLoginEnabled: false,
browserLoginAutoConnect: false,
};
const onLabelEvent = 'on';
const removeListenerLabelEvent = 'removeListener';
@ -26,17 +27,6 @@ describe('welcome', () => {
expect(spy).toBeCalledWith(welcomeLabel, expect.any(Function));
});
it('should remove listener `welcome` when component is unmounted', () => {
const spyMount = jest.spyOn(ipcRenderer, onLabelEvent);
const spyUnmount = jest.spyOn(ipcRenderer, removeListenerLabelEvent);
const wrapper = shallow(React.createElement(Welcome));
expect(spyMount).toBeCalledWith(welcomeLabel, expect.any(Function));
wrapper.unmount();
expect(spyUnmount).toBeCalledWith(welcomeLabel, expect.any(Function));
});
it('should call `updateState` when component is mounted', () => {
const spy = jest.spyOn(Welcome.prototype, 'setState');
shallow(React.createElement(Welcome));
@ -113,11 +103,24 @@ describe('welcome', () => {
message: '',
urlValid: true,
isPodConfigured: true,
isSeamlessLoginEnabled: true,
isBrowserLoginEnabled: false,
browserLoginAutoConnect: false,
isLoading: false,
};
const wrapper = shallow(React.createElement(Welcome));
ipcRenderer.send('welcome', welcomeMock);
const podUrlBox = `input.Welcome-main-container-podurl-box`;
expect(wrapper.find(podUrlBox).getElements()).toEqual([]);
});
it('should remove listener `welcome` when component is unmounted', () => {
const spyMount = jest.spyOn(ipcRenderer, onLabelEvent);
const spyUnmount = jest.spyOn(ipcRenderer, removeListenerLabelEvent);
const wrapper = shallow(React.createElement(Welcome));
expect(spyMount).toBeCalledWith(welcomeLabel, expect.any(Function));
wrapper.unmount();
expect(spyUnmount).toBeCalledWith(welcomeLabel, expect.any(Function));
});
});

View File

@ -517,6 +517,7 @@ export class AppMenu {
enabled:
!bringToFrontCC || bringToFrontCC === CloudConfigDataTypes.NOT_SET,
},
this.buildSeparator(),
{
type: 'checkbox',
label: i18n.t('Browser login')(),

View File

@ -95,6 +95,9 @@ export class AutoUpdate {
data: info,
});
}
if (isMac) {
config.backupGlobalConfig();
}
});
this.autoUpdater.on('error', (error) => {

View File

@ -58,7 +58,8 @@ export interface IConfig {
installVariant?: string;
bootCount?: number;
startedAfterAutoUpdate?: boolean;
enableBrowserLogin: boolean;
enableBrowserLogin?: boolean;
browserLoginAutoConnect?: boolean;
}
export interface IGlobalConfig {

View File

@ -69,7 +69,7 @@ const broadcastMessage = (method, data) => {
mainEvents.publish(apiCmds.onSwiftSearchMessage, [method, data]);
};
const getSeamlessLoginUrl = (pod: string) =>
const getBrowserLoginUrl = (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';
/**
@ -384,9 +384,14 @@ ipcMain.on(
mainWebContents.focus();
}
break;
case apiCmds.seamlessLogin:
case apiCmds.browserLogin:
await config.updateUserConfig({
browserLoginAutoConnect: arg.browserLoginAutoConnect,
});
if (!arg.isPodConfigured) {
await config.updateUserConfig({ url: arg.newPodUrl });
await config.updateUserConfig({
url: arg.newPodUrl,
});
}
const urlFromCmd = getCommandLineArgs(process.argv, '--url=', false);
const { url: userConfigURL } = config.getUserConfigFields(['url']);
@ -398,7 +403,7 @@ ipcMain.on(
: globalConfigURL;
const { subdomain, domain, tld } = whitelistHandler.parseDomain(podUrl);
const formattedPodUrl = `https://${subdomain}.${domain}${tld}`;
const loginUrl = getSeamlessLoginUrl(formattedPodUrl);
const loginUrl = getBrowserLoginUrl(formattedPodUrl);
logger.info(
'main-api-handler:',
'check if sso is enabled for the pod',
@ -408,19 +413,19 @@ ipcMain.on(
const authResponse = (await response.json()) as IAuthResponse;
logger.info('main-api-handler:', 'check auth response', authResponse);
if (
arg.isSeamlessLoginEnabled &&
arg.isBrowserLoginEnabled &&
authResponse.authenticationType === 'sso'
) {
logger.info(
'main-api-handler:',
'seamless login is enabled - logging in',
'browser 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',
'browser login is not enabled - loading main window with',
formattedPodUrl,
);
const mainWebContents = windowHandler.getMainWebContents();

View File

@ -63,7 +63,7 @@ class ProtocolHandler {
);
// Handle protocol for Seamless login
if (url?.includes('skey') && url?.includes('anticsrf')) {
await this.handleSeamlessLogin(url);
await this.handleBrowserLogin(url);
return;
}
@ -112,11 +112,12 @@ class ProtocolHandler {
/**
* Sets session cookies and navigates to the pod url
*/
public async handleSeamlessLogin(protocolUri: string): Promise<void> {
public async handleBrowserLogin(protocolUri: string): Promise<void> {
const globalConfig = config.getGlobalConfigFields(['url']);
const userConfig = config.getUserConfigFields(['url']);
const url = userConfig.url ? userConfig.url : globalConfig.url;
const { subdomain, tld, domain } = whitelistHandler.parseDomain(url);
const redirectURL = userConfig.url ? userConfig.url : globalConfig.url;
const { subdomain, tld, domain } =
whitelistHandler.parseDomain(redirectURL);
const cookieDomain = `.${subdomain}.${domain}${tld}`;
if (protocolUri) {
const urlParams = new URLSearchParams(new URL(protocolUri).search);
@ -124,7 +125,7 @@ class ProtocolHandler {
const anticsrfValue = urlParams.get('anticsrf');
if (skeyValue && anticsrfValue) {
const skeyCookie: CookiesSetDetails = {
url,
url: redirectURL,
name: 'skey',
value: skeyValue,
secure: true,
@ -133,7 +134,7 @@ class ProtocolHandler {
domain: cookieDomain,
};
const csrfCookie: CookiesSetDetails = {
url,
url: redirectURL,
name: 'anti-csrf-cookie',
value: anticsrfValue,
secure: true,
@ -152,10 +153,13 @@ class ProtocolHandler {
}
}
const mainWebContents = windowHandler.getMainWebContents();
if (mainWebContents && !mainWebContents?.isDestroyed() && url) {
logger.info('protocol-handler: redirecting main webContents ', url);
windowHandler.setMainWindowOrigin(url);
mainWebContents?.loadURL(url);
if (mainWebContents && !mainWebContents?.isDestroyed() && redirectURL) {
logger.info(
'protocol-handler: redirecting main webContents ',
redirectURL,
);
windowHandler.setMainWindowOrigin(redirectURL);
mainWebContents?.loadURL(redirectURL);
}
}
}

View File

@ -14,7 +14,7 @@ import { isMac, isWindowsOS } from '../../common/env';
export class PresenceStatus {
private presenceStatus: IStatusBadge = {
statusCategory: EPresenceStatusCategory.NO_PRESENCE,
statusGroup: EPresenceStatusGroup.OFFLINE,
statusGroup: EPresenceStatusGroup.HIDE_PRESENCE,
count: 0,
};
private tray: ITray = {

View File

@ -222,6 +222,7 @@ export class WindowHandler {
'clientSwitch',
'enableRendererLogs',
'enableBrowserLogin',
'browserLoginAutoConnect',
]);
logger.info(
`window-handler: main windows initialized with following config data`,
@ -252,7 +253,7 @@ export class WindowHandler {
this.isPodConfigured = !config.isFirstTimeLaunch();
this.didShowWelcomeScreen = false;
this.shouldShowWelcomeScreen =
config.isFirstTimeLaunch() || this.config.enableBrowserLogin;
config.isFirstTimeLaunch() || !!this.config.enableBrowserLogin;
this.windowOpts = {
...this.getWindowOpts(
@ -566,7 +567,8 @@ export class WindowHandler {
message: '',
urlValid: !!userConfigUrl,
isPodConfigured: this.isPodConfigured && !!userConfigUrl,
isSeamlessLoginEnabled: this.config.enableBrowserLogin,
isBrowserLoginEnabled: this.config.enableBrowserLogin,
browserLoginAutoConnect: this.config.browserLoginAutoConnect,
});
this.didShowWelcomeScreen = true;
this.mainWebContents.focus();
@ -860,11 +862,6 @@ export class WindowHandler {
return;
}
logger.info(`finished loading welcome screen.`);
const ssoValue = !!(
this.userConfig.url &&
this.userConfig.url.indexOf('/login/sso/initsso') > -1
);
this.welcomeScreenWindow.webContents.send('page-load-welcome', {
locale: i18n.getLocale(),
resource: i18n.loadedResources,
@ -882,7 +879,6 @@ export class WindowHandler {
url: userConfigUrl || this.startUrl,
message: '',
urlValid: !!userConfigUrl,
sso: ssoValue,
});
this.appMenu = new AppMenu();
this.addWindow(opts.winKey, this.welcomeScreenWindow);

View File

@ -72,7 +72,7 @@ export enum apiCmds {
updateAndRestart = 'update-and-restart',
downloadUpdate = 'download-update',
checkForUpdates = 'check-for-updates',
seamlessLogin = 'seamless-login',
browserLogin = 'browser-login',
updateMyPresence = 'update-my-presence',
getMyPresence = 'get-my-presence',
updateSymphonyTray = 'update-system-tray',
@ -125,7 +125,8 @@ export interface IApiArgs {
newPodUrl: string;
startUrl: string;
isPodConfigured: boolean;
isSeamlessLoginEnabled: boolean;
isBrowserLoginEnabled: boolean;
browserLoginAutoConnect: boolean;
swiftSearchData: any;
types: string[];
thumbnailSize: Size;

View File

@ -223,6 +223,7 @@
"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",
" and more than": " and more than",
" 1,000 institutions.": " 1,000 institutions.",
"Log in with your pod URL": "Log in with your pod URL",
"Youll momentarily be redirected to your web browser.": "Youll momentarily be redirected to your web browser.",
@ -231,7 +232,9 @@
"Pod URL": "Pod URL",
"SSO": "SSO",
"Symphony Logo": "Symphony",
"WelcomeText": "Welcome"
"WelcomeText": "Welcome",
"Automatically redirect to your web browser on launch": "Automatically redirect to your web browser on launch",
"Retry": "Retry"
},
"Window": "Window",
"Would you like to restart and apply these new settings now?": "Would you like to restart and apply these new settings now?",

View File

@ -223,6 +223,7 @@
"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",
" and more than": " and more than",
" 1,000 institutions.": " 1,000 institutions.",
"Log in with your pod URL": "Log in with your pod URL",
"Youll momentarily be redirected to your web browser.": "Youll momentarily be redirected to your web browser.",
@ -231,7 +232,9 @@
"Pod URL": "Pod URL",
"SSO": "SSO",
"Symphony Logo": "Symphony",
"WelcomeText": "Welcome"
"WelcomeText": "Welcome",
"Automatically redirect to your web browser on launch": "Automatically redirect to your web browser on launch",
"Retry": "Retry"
},
"Window": "Window",
"Would you like to restart and apply these new settings now?": "Would you like to restart and apply these new settings now?",

View File

@ -223,6 +223,7 @@
"Find your pod URL in your invitation email.": "L'URL de votre instance Symphony est indiquée dans l'email d'invitation.",
"Welcome to the largest global community in financial services with over": "Bienvenue dans la communauté des services financiers la plus large au monde avec plus d'",
" half a million users": " un demi million d'usagers",
" and more than": " et plus de",
" 1,000 institutions.": " 1000 institutions.",
"Log in with your pod URL": "Connectez-vous avec l'URL de votre instance",
"Youll momentarily be redirected to your web browser.": "Vous allez être redirigé(e) brièvement vers votre navigateur internet.",

View File

@ -223,6 +223,7 @@
"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",
" and more than": " et plus de",
" 1,000 institutions.": " 1,000 institutions.",
"Log in with your pod URL": "Log in with your pod URL",
"Youll momentarily be redirected to your web browser.": "Youll momentarily be redirected to your web browser.",

View File

@ -223,6 +223,7 @@
"Find your pod URL in your invitation email.": "招待メールでpod URLをご確認ください。",
"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",
" and more than": " and more than",
" 1,000 institutions.": " 1,000 institutions.",
"Log in with your pod URL": "Pod URLからログインください",
"Youll momentarily be redirected to your web browser.": "まもなくウェブブラウザーにリダイレクトさせていただきます。",

View File

@ -223,6 +223,7 @@
"Find your pod URL in your invitation email.": "招待メールでpod URLをご確認ください。",
"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",
" and more than": " and more than",
" 1,000 institutions.": " 1,000 institutions.",
"Log in with your pod URL": "Pod URLからログインください",
"Youll momentarily be redirected to your web browser.": "まもなくウェブブラウザーにリダイレクトさせていただきます。",

View File

@ -8,19 +8,23 @@ interface IState {
message: string;
urlValid: boolean;
isPodConfigured: boolean;
isSeamlessLoginEnabled: boolean;
isBrowserLoginEnabled: boolean;
browserLoginAutoConnect: boolean;
isLoading: boolean;
}
const WELCOME_NAMESPACE = 'Welcome';
const DEFAULT_MESSAGE = 'Find your pod URL in your invitation email.';
const DEFAULT_POD_URL = 'https://[POD].symphony.com';
const BROWSER_LOGIN_AUTO_REDIRECT_TIMEOUT = 2000;
export default class Welcome extends React.Component<{}, IState> {
private readonly eventHandlers = {
onLogin: () => this.login(),
};
private browserLoginTimeoutId: NodeJS.Timeout | undefined;
constructor(props) {
super(props);
this.state = {
@ -28,7 +32,8 @@ export default class Welcome extends React.Component<{}, IState> {
message: '',
urlValid: false,
isPodConfigured: false,
isSeamlessLoginEnabled: true,
isBrowserLoginEnabled: true,
browserLoginAutoConnect: false,
isLoading: false,
};
this.updateState = this.updateState.bind(this);
@ -38,33 +43,44 @@ export default class Welcome extends React.Component<{}, IState> {
* Render the component
*/
public render(): JSX.Element {
const { url, message, isPodConfigured } = this.state;
const { url, message, isPodConfigured, isLoading, isBrowserLoginEnabled } =
this.state;
return (
<div className='Welcome' lang={i18n.getLocale()}>
<div className='Welcome-content'>
<div className='Welcome-image-container'>
{this.getWelcomeImage()}
</div>
<div
className='Welcome-about-symphony-text'
style={{ marginTop: isPodConfigured ? '35px' : '8px' }}
>
<span>
{i18n.t(
'Welcome to the largest global community in financial services with over',
WELCOME_NAMESPACE,
)()}
</span>
<span className='Welcome-text-bold'>
{i18n.t(' half a million users', WELCOME_NAMESPACE)()}
</span>
<span>{i18n.t(' and more than', WELCOME_NAMESPACE)()}</span>
<span className='Welcome-text-bold'>
{i18n.t(' 1,000 institutions.', WELCOME_NAMESPACE)()}
</span>
</div>
{isPodConfigured && (
<React.Fragment>
<span className='Welcome-welcome-back'>
{i18n.t('Welcome back!', WELCOME_NAMESPACE)()}
</span>
<span className='Welcome-login-label'>
{i18n.t('Please login to continue', WELCOME_NAMESPACE)()}
</span>
</React.Fragment>
)}
{!isPodConfigured && (
<div>
<React.Fragment>
<div
className='Welcome-about-symphony-text'
style={{ marginTop: isPodConfigured ? '35px' : '8px' }}
>
<span>
{i18n.t(
'Welcome to the largest global community in financial services with over',
WELCOME_NAMESPACE,
)()}
</span>
<span className='Welcome-text-bold'>
{i18n.t(' half a million users', WELCOME_NAMESPACE)()}
</span>
<span>{i18n.t(' and more than', WELCOME_NAMESPACE)()}</span>
<span className='Welcome-text-bold'>
{i18n.t(' 1,000 institutions.', WELCOME_NAMESPACE)()}
</span>
</div>
<div className='Welcome-login-text'>
<span>
{i18n.t('Log in with your pod URL', WELCOME_NAMESPACE)()}
@ -74,6 +90,7 @@ export default class Welcome extends React.Component<{}, IState> {
<span>{i18n.t('Pod URL', WELCOME_NAMESPACE)()}</span>
<div>
<input
disabled={isLoading}
data-testid={'Welcome-main-container-podurl-box'}
className='Welcome-main-container-podurl-box'
tabIndex={0}
@ -88,19 +105,29 @@ export default class Welcome extends React.Component<{}, IState> {
</label>
</div>
</div>
</div>
</React.Fragment>
)}
{this.renderLoginButton()}
<div className='Welcome-redirect-info-text-container'>
<span>
{i18n.t(
'Youll momentarily be redirected to your web browser.',
WELCOME_NAMESPACE,
)()}
</span>
</div>
{isBrowserLoginEnabled && (
<div className='Welcome-redirect-info-text-container'>
<span>
{i18n.t(
'Youll momentarily be redirected to your web browser.',
WELCOME_NAMESPACE,
)()}
</span>
{isLoading && (
<button
className='Welcome-retry-button'
onClick={this.eventHandlers.onLogin}
>
{i18n.t('Retry', WELCOME_NAMESPACE)()}
</button>
)}
</div>
)}
</div>
</div>
);
@ -110,7 +137,18 @@ export default class Welcome extends React.Component<{}, IState> {
* Perform actions on component being mounted
*/
public componentDidMount(): void {
ipcRenderer.on('welcome', this.updateState);
ipcRenderer.on('welcome', (_event, data) => {
if (
data.isPodConfigured &&
data.browserLoginAutoConnect &&
data.isBrowserLoginEnabled
) {
this.browserLoginTimeoutId = setTimeout(() => {
this.login();
}, BROWSER_LOGIN_AUTO_REDIRECT_TIMEOUT);
}
this.updateState(_event, data);
});
}
/**
@ -125,12 +163,18 @@ export default class Welcome extends React.Component<{}, IState> {
*/
public login(): void {
this.setState({ isLoading: true });
const { url, isPodConfigured, isSeamlessLoginEnabled } = this.state;
const {
url,
isPodConfigured,
isBrowserLoginEnabled,
browserLoginAutoConnect,
} = this.state;
ipcRenderer.send(apiName.symphonyApi, {
cmd: apiCmds.seamlessLogin,
cmd: apiCmds.browserLogin,
newPodUrl: url,
isPodConfigured,
isSeamlessLoginEnabled,
isBrowserLoginEnabled,
browserLoginAutoConnect,
});
}
@ -159,13 +203,73 @@ export default class Welcome extends React.Component<{}, IState> {
});
}
/**
* Update pod url from the text box
* @param event
*/
public updateBrowserLoginAutoConnect(event) {
const { urlValid } = this.state;
const browserLoginAutoConnect = event.target.checked;
if (!browserLoginAutoConnect) {
if (this.browserLoginTimeoutId) {
clearTimeout(this.browserLoginTimeoutId);
}
this.setState({
browserLoginAutoConnect,
});
}
if (urlValid && browserLoginAutoConnect) {
this.setState({
browserLoginAutoConnect,
isLoading: true,
});
const { url, isPodConfigured, isBrowserLoginEnabled } = this.state;
ipcRenderer.send(apiName.symphonyApi, {
cmd: apiCmds.browserLogin,
newPodUrl: url,
isPodConfigured,
isBrowserLoginEnabled,
browserLoginAutoConnect,
});
}
}
/**
* Renders login button content
*/
private renderLoginButton() {
const { isLoading, isPodConfigured, urlValid } = this.state;
if (!isLoading) {
return (
const {
isLoading,
isPodConfigured,
urlValid,
isBrowserLoginEnabled,
browserLoginAutoConnect,
} = this.state;
return (
<React.Fragment>
{isBrowserLoginEnabled && (
<div className='Welcome-auto-connect-wrapper'>
<label className='switch'>
<input
type='checkbox'
disabled={isLoading}
checked={browserLoginAutoConnect}
onChange={this.updateBrowserLoginAutoConnect.bind(this)}
/>
<span className='slider round'></span>
</label>
<div className='auto-connect-labels'>
<span className='auto-connect-label'>
{i18n.t(
'Automatically redirect to your web browser on launch',
WELCOME_NAMESPACE,
)()}
</span>
</div>
</div>
)}
<button
className='Welcome-continue-button'
tabIndex={1}
@ -173,29 +277,29 @@ export default class Welcome extends React.Component<{}, IState> {
onClick={this.eventHandlers.onLogin}
style={isPodConfigured ? { marginTop: '40px' } : {}}
>
{i18n.t('log in', WELCOME_NAMESPACE)()}
{isLoading && (
<div className='splash-screen--spinner-container'>
<div
className='splash-screen--spinner'
role='progressbar'
data-testid='SPLASH_SCREEN_SPINNER'
>
<svg viewBox='22 22 44 44'>
<circle
className='splash-screen--spinner-circle'
cx='44'
cy='44'
r='20.2'
fill='none'
stroke-width='3.6'
></circle>
</svg>
</div>
</div>
)}
{!isLoading && i18n.t('log in', WELCOME_NAMESPACE)()}
</button>
);
}
return (
<div className='splash-screen--spinner-container'>
<div
className='splash-screen--spinner'
role='progressbar'
data-testid='SPLASH_SCREEN_SPINNER'
>
<svg viewBox='22 22 44 44'>
<circle
className='splash-screen--spinner-circle'
cx='44'
cy='44'
r='20.2'
fill='none'
stroke-width='3.6'
></circle>
</svg>
</div>
</div>
</React.Fragment>
);
}

View File

@ -7,11 +7,13 @@
// Color palette
@electricity-ui-05: #e9f2f9;
@electricity-ui-50: #0277d6;
@electricity-ui-40: #2996fd;
@electricity-ui-20: #aad4f8;
@electricity-ui-30: #6eb9fd;
@electricity-ui-40: #2996fd;
@electricity-ui-50: #0277d6;
@electricity-ui-60: #27588e;
@graphite-10: #e3e5e7;
@graphite-20: #cdcfd4;
@graphite-05: #f1f1f3;
@graphite-80: #27292c;

View File

@ -220,10 +220,10 @@ body {
text-decoration: none;
line-height: 12px;
background-color: @electricity-ui-50;
color: #ffffff;
color: @text-color-primary;
cursor: pointer;
text-transform: uppercase;
margin: 24px 0 4px 0;
margin: 16px 0 4px 0;
&:focus {
box-shadow: 0 0 10px rgba(61, 162, 253, 1);
@ -236,16 +236,61 @@ body {
}
&-redirect-info-text-container {
margin-top: 32px;
margin-top: 40px;
margin-bottom: 16px;
span {
font-weight: 400;
font-size: 12px;
font-size: 14px;
line-height: 16px;
text-align: center;
color: @graphite-30;
}
}
&-retry-button {
box-shadow: none;
border: none;
cursor: pointer;
padding: 0;
margin: 0 8px;
font-size: 14px;
text-align: center;
display: inline-block;
text-decoration: none;
background-color: transparent;
color: @electricity-ui-30;
}
&-auto-connect-wrapper {
display: flex;
margin-top: 18px;
.auto-connect-labels {
display: flex;
flex-direction: column;
.auto-connect-label {
font-size: 14px;
color: @graphite-10;
margin-bottom: 8px;
font-weight: 300;
}
.auto-connect-help-label {
font-size: 12px;
color: @graphite-30;
}
}
}
&-welcome-back {
font-size: 14px;
color: @graphite-05;
margin-top: 40px;
}
&-login-label {
font-size: 18px;
color: @graphite-05;
margin-top: 13px;
}
}
@keyframes fade-in-logo {
@ -313,15 +358,14 @@ body {
.splash-screen--spinner-container {
opacity: 0;
animation: fade-in-spinner 1s ease-in 0s forwards;
margin-top: 2rem;
text-align: center;
}
.splash-screen--spinner {
display: inline-block;
line-height: 1;
color: #0277d6;
width: 40px;
height: 40px;
color: @electricity-ui-50;
width: 20px;
height: 20px;
animation: progress-circular-rotate 1.4s linear infinite;
}
.splash-screen--spinner-circle {
@ -330,3 +374,80 @@ body {
stroke-dashoffset: 0px;
stroke: currentColor;
}
.switch {
margin: auto;
margin-right: 8px;
position: relative;
display: inline-block;
width: 43px;
height: 8px;
}
/* Hide default HTML checkbox */
.switch input {
opacity: 0;
width: 0;
height: 0;
}
/* The slider */
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
-webkit-transition: 0.4s;
transition: 0.4s;
background-color: @graphite-60;
}
.slider:before {
position: absolute;
content: '';
height: 14px;
width: 14px;
bottom: -5px;
-webkit-transition: 0.4s;
transition: 0.4s;
background-color: @graphite-80;
border: 2px solid @electricity-ui-40;
&:disabled {
background-color: #090a0b;
}
}
input:checked + .slider:before {
-webkit-transform: translateX(20px);
-ms-transform: translateX(20px);
transform: translateX(20px);
background-color: @electricity-ui-40;
}
input:not(:checked) + .slider {
&:hover {
&::before {
border-color: @electricity-ui-30;
}
}
}
input:checked + .slider {
&:hover {
&::before {
background-color: @electricity-ui-30;
border-color: @electricity-ui-30;
}
}
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}