mirror of
https://github.com/bitwarden/browser
synced 2025-12-18 17:23:37 +00:00
[Account Switching] [Feature] Add the ability to maintain state for up to 5 accounts at once (#1079)
* [refactor] Remove references to deprecated services * [feature] Implement account switching * [bug] Fix state handling for authentication dependent system menu items * [bug] Enable the account switcher to fucntion properly when switching to a locked accounts * [feature] Enable locking any account from the menu * [bug] Ensure the avatar instance used in the account switcher updates on account change * [style] Fix lint complaints * [bug] Ensure the logout command callback can handle any user in state * [style] Fix lint complaints * rollup * [style] Fix lint complaints * [bug] Don't clean up state until everything else is done on logout * [bug] Navigate to vault on a succesful account switch * [bug] Init the state service on start * [feature] Limit account switching to 5 account maximum * [bug] Resolve app lock state with 5 logged out accounts * [chore] Update account refrences to match recent jslib restructuring * [bug] Add missing awaits * [bug] Update app menu on logout * [bug] Hide the switcher if there are no authed accounts * [bug] Move authenticationStatus display information out of jslib * [bug] Remove unused active style from scss * [refactor] Rewrite the menu bar * [style] Fix lint complaints * [bug] Clean state of loggout out user after redirect * [bug] Redirect on logout if not explicity provided a userId that isn't active * [bug] Relocated several settings items to persistant storage * [bug] Correct account switcher styles on all themes * [chore] Include state migration service in services * [bug] Swap to next account on logout * [bug] Correct DI service * [bug] fix loginGuard deps in services.module * [chore] update jslib * [bug] Remove badly merged scss * [chore] update jslib * [review] Code review cleanup * [review] Code review cleanup Co-authored-by: Hinton <oscar@oscarhinton.com>
This commit is contained in:
@@ -1,525 +1,54 @@
|
||||
import {
|
||||
app,
|
||||
BrowserWindow,
|
||||
clipboard,
|
||||
dialog,
|
||||
ipcMain,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuItemConstructorOptions,
|
||||
shell,
|
||||
} from 'electron';
|
||||
|
||||
import { Main } from '../main';
|
||||
|
||||
import { BaseMenu } from 'jslib-electron/baseMenu';
|
||||
|
||||
import { isMacAppStore, isSnapStore, isWindowsStore } from 'jslib-electron/utils';
|
||||
import { MenuUpdateRequest } from './menu.updater';
|
||||
import { Menubar } from './menubar';
|
||||
|
||||
import { ConstantsService } from 'jslib-common/services/constants.service';
|
||||
const cloudWebVaultUrl: string = 'https://vault.bitwarden.com';
|
||||
|
||||
export class MenuMain extends BaseMenu {
|
||||
menu: Menu;
|
||||
updateMenuItem: MenuItem;
|
||||
addNewLogin: MenuItem;
|
||||
addNewItem: MenuItem;
|
||||
addNewFolder: MenuItem;
|
||||
syncVault: MenuItem;
|
||||
exportVault: MenuItem;
|
||||
settings: MenuItem;
|
||||
lockNow: MenuItem;
|
||||
logOut: MenuItem;
|
||||
twoStepLogin: MenuItem;
|
||||
fingerprintPhrase: MenuItem;
|
||||
changeMasterPass: MenuItem;
|
||||
premiumMembership: MenuItem;
|
||||
passwordGenerator: MenuItem;
|
||||
passwordHistory: MenuItem;
|
||||
searchVault: MenuItem;
|
||||
copyUsername: MenuItem;
|
||||
copyPassword: MenuItem;
|
||||
copyTotp: MenuItem;
|
||||
unlockedRequiredMenuItems: MenuItem[] = [];
|
||||
|
||||
constructor(private main: Main) {
|
||||
super(main.i18nService, main.windowMain);
|
||||
}
|
||||
|
||||
init() {
|
||||
this.initProperties();
|
||||
async init() {
|
||||
this.initContextMenu();
|
||||
this.initApplicationMenu();
|
||||
|
||||
this.updateMenuItem = this.menu.getMenuItemById('checkForUpdates');
|
||||
this.addNewLogin = this.menu.getMenuItemById('addNewLogin');
|
||||
this.addNewItem = this.menu.getMenuItemById('addNewItem');
|
||||
this.addNewFolder = this.menu.getMenuItemById('addNewFolder');
|
||||
this.syncVault = this.menu.getMenuItemById('syncVault');
|
||||
this.exportVault = this.menu.getMenuItemById('exportVault');
|
||||
this.settings = this.menu.getMenuItemById('settings');
|
||||
this.lockNow = this.menu.getMenuItemById('lockNow');
|
||||
this.logOut = this.menu.getMenuItemById('logOut');
|
||||
this.twoStepLogin = this.menu.getMenuItemById('twoStepLogin');
|
||||
this.fingerprintPhrase = this.menu.getMenuItemById('fingerprintPhrase');
|
||||
this.changeMasterPass = this.menu.getMenuItemById('changeMasterPass');
|
||||
this.premiumMembership = this.menu.getMenuItemById('premiumMembership');
|
||||
this.passwordGenerator = this.menu.getMenuItemById('passwordGenerator');
|
||||
this.passwordHistory = this.menu.getMenuItemById('passwordHistory');
|
||||
this.searchVault = this.menu.getMenuItemById('searchVault');
|
||||
this.copyUsername = this.menu.getMenuItemById('copyUsername');
|
||||
this.copyPassword = this.menu.getMenuItemById('copyPassword');
|
||||
this.copyTotp = this.menu.getMenuItemById('copyTotp');
|
||||
|
||||
this.unlockedRequiredMenuItems = [
|
||||
this.addNewLogin, this.addNewItem, this.addNewFolder,
|
||||
this.syncVault, this.exportVault, this.settings, this.lockNow, this.twoStepLogin, this.fingerprintPhrase,
|
||||
this.changeMasterPass, this.premiumMembership, this.passwordGenerator, this.passwordHistory,
|
||||
this.searchVault, this.copyUsername, this.copyPassword];
|
||||
this.updateApplicationMenuState(false, true, false);
|
||||
await this.setMenu();
|
||||
}
|
||||
|
||||
updateApplicationMenuState(isAuthenticated: boolean, isLocked: boolean, hideChangeMasterPass: boolean) {
|
||||
if (isAuthenticated != null && isLocked != null) {
|
||||
this.unlockedRequiredMenuItems.forEach((mi: MenuItem) => {
|
||||
if (mi != null) {
|
||||
mi.enabled = isAuthenticated && !isLocked;
|
||||
}
|
||||
});
|
||||
|
||||
if (this.logOut != null) {
|
||||
this.logOut.enabled = isAuthenticated;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.changeMasterPass != null) {
|
||||
this.changeMasterPass.visible = !(hideChangeMasterPass ?? false);
|
||||
}
|
||||
|
||||
if (this.menu != null) {
|
||||
Menu.setApplicationMenu(this.menu);
|
||||
}
|
||||
async updateApplicationMenuState(updateRequest: MenuUpdateRequest) {
|
||||
await this.setMenu(updateRequest);
|
||||
}
|
||||
|
||||
private initApplicationMenu() {
|
||||
const accountSubmenu: MenuItemConstructorOptions[] = [
|
||||
{
|
||||
label: this.main.i18nService.t('changeMasterPass'),
|
||||
id: 'changeMasterPass',
|
||||
click: async () => {
|
||||
const result = await dialog.showMessageBox(this.main.windowMain.win, {
|
||||
title: this.main.i18nService.t('changeMasterPass'),
|
||||
message: this.main.i18nService.t('changeMasterPass'),
|
||||
detail: this.main.i18nService.t('changeMasterPasswordConfirmation'),
|
||||
buttons: [this.main.i18nService.t('yes'), this.main.i18nService.t('no')],
|
||||
cancelId: 1,
|
||||
defaultId: 0,
|
||||
noLink: true,
|
||||
});
|
||||
if (result.response === 0) {
|
||||
await this.openWebVault();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: this.main.i18nService.t('twoStepLogin'),
|
||||
id: 'twoStepLogin',
|
||||
click: async () => {
|
||||
const result = await dialog.showMessageBox(this.main.windowMain.win, {
|
||||
title: this.main.i18nService.t('twoStepLogin'),
|
||||
message: this.main.i18nService.t('twoStepLogin'),
|
||||
detail: this.main.i18nService.t('twoStepLoginConfirmation'),
|
||||
buttons: [this.main.i18nService.t('yes'), this.main.i18nService.t('no')],
|
||||
cancelId: 1,
|
||||
defaultId: 0,
|
||||
noLink: true,
|
||||
});
|
||||
if (result.response === 0) {
|
||||
await this.openWebVault();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: this.main.i18nService.t('fingerprintPhrase'),
|
||||
id: 'fingerprintPhrase',
|
||||
click: () => this.main.messagingService.send('showFingerprintPhrase'),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: this.i18nService.t('logOut'),
|
||||
id: 'logOut',
|
||||
click: async () => {
|
||||
const result = await dialog.showMessageBox(this.windowMain.win, {
|
||||
title: this.i18nService.t('logOut'),
|
||||
message: this.i18nService.t('logOut'),
|
||||
detail: this.i18nService.t('logOutConfirmation'),
|
||||
buttons: [this.i18nService.t('logOut'), this.i18nService.t('cancel')],
|
||||
cancelId: 1,
|
||||
defaultId: 0,
|
||||
noLink: true,
|
||||
});
|
||||
if (result.response === 0) {
|
||||
this.main.messagingService.send('logout');
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
this.editMenuItemOptions.submenu = (this.editMenuItemOptions.submenu as MenuItemConstructorOptions[]).concat([
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: this.main.i18nService.t('copyUsername'),
|
||||
id: 'copyUsername',
|
||||
click: () => this.main.messagingService.send('copyUsername'),
|
||||
accelerator: 'CmdOrCtrl+U',
|
||||
},
|
||||
{
|
||||
label: this.main.i18nService.t('copyPassword'),
|
||||
id: 'copyPassword',
|
||||
click: () => this.main.messagingService.send('copyPassword'),
|
||||
accelerator: 'CmdOrCtrl+P',
|
||||
},
|
||||
{
|
||||
label: this.main.i18nService.t('copyVerificationCodeTotp'),
|
||||
id: 'copyTotp',
|
||||
click: () => this.main.messagingService.send('copyTotp'),
|
||||
accelerator: 'CmdOrCtrl+T',
|
||||
},
|
||||
]);
|
||||
|
||||
if (!isWindowsStore() && !isMacAppStore()) {
|
||||
accountSubmenu.unshift({
|
||||
label: this.main.i18nService.t('premiumMembership'),
|
||||
click: () => this.main.messagingService.send('openPremium'),
|
||||
id: 'premiumMembership',
|
||||
});
|
||||
}
|
||||
|
||||
let helpSubmenu: MenuItemConstructorOptions[] = [
|
||||
{
|
||||
label: this.main.i18nService.t('emailUs'),
|
||||
click: () => shell.openExternal('mailTo:hello@bitwarden.com'),
|
||||
},
|
||||
{
|
||||
label: this.main.i18nService.t('visitOurWebsite'),
|
||||
click: () => shell.openExternal('https://bitwarden.com/contact'),
|
||||
},
|
||||
{
|
||||
label: this.main.i18nService.t('fileBugReport'),
|
||||
click: () => shell.openExternal('https://github.com/bitwarden/desktop/issues'),
|
||||
},
|
||||
];
|
||||
|
||||
if (isMacAppStore()) {
|
||||
helpSubmenu.push({
|
||||
label: this.main.i18nService.t('legal'),
|
||||
submenu: [
|
||||
{
|
||||
label: this.main.i18nService.t('termsOfService'),
|
||||
click: () => shell.openExternal('https://bitwarden.com/terms/'),
|
||||
},
|
||||
{
|
||||
label: this.main.i18nService.t('privacyPolicy'),
|
||||
click: () => shell.openExternal('https://bitwarden.com/privacy/'),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
helpSubmenu = helpSubmenu.concat([
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: this.main.i18nService.t('followUs'),
|
||||
submenu: [
|
||||
{
|
||||
label: this.main.i18nService.t('blog'),
|
||||
click: () => shell.openExternal('https://blog.bitwarden.com'),
|
||||
},
|
||||
{
|
||||
label: 'Twitter',
|
||||
click: () => shell.openExternal('https://twitter.com/bitwarden'),
|
||||
},
|
||||
{
|
||||
label: 'Facebook',
|
||||
click: () => shell.openExternal('https://www.facebook.com/bitwarden/'),
|
||||
},
|
||||
{
|
||||
label: 'GitHub',
|
||||
click: () => shell.openExternal('https://github.com/bitwarden'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: this.main.i18nService.t('goToWebVault'),
|
||||
click: async () => await this.openWebVault(),
|
||||
},
|
||||
]);
|
||||
|
||||
if (!isWindowsStore()) {
|
||||
helpSubmenu.push({
|
||||
label: this.main.i18nService.t('getMobileApp'),
|
||||
submenu: [
|
||||
{
|
||||
label: 'iOS',
|
||||
click: () => {
|
||||
shell.openExternal('https://itunes.apple.com/app/' +
|
||||
'bitwarden-free-password-manager/id1137397744?mt=8');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Android',
|
||||
click: () => {
|
||||
shell.openExternal('https://play.google.com/store/apps/' +
|
||||
'details?id=com.x8bit.bitwarden');
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
helpSubmenu.push({
|
||||
label: this.main.i18nService.t('getBrowserExtension'),
|
||||
submenu: [
|
||||
{
|
||||
label: 'Chrome',
|
||||
click: () => {
|
||||
shell.openExternal('https://chrome.google.com/webstore/detail/' +
|
||||
'bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Firefox',
|
||||
click: () => {
|
||||
shell.openExternal('https://addons.mozilla.org/firefox/addon/' +
|
||||
'bitwarden-password-manager/');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Opera',
|
||||
click: () => {
|
||||
shell.openExternal('https://addons.opera.com/extensions/details/' +
|
||||
'bitwarden-free-password-manager/');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Edge',
|
||||
click: () => {
|
||||
shell.openExternal('https://microsoftedge.microsoft.com/addons/' +
|
||||
'detail/jbkfoedolllekgbhcbcoahefnbanhhlh');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Safari',
|
||||
click: () => {
|
||||
shell.openExternal('https://bitwarden.com/download/');
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
const template: MenuItemConstructorOptions[] = [
|
||||
{
|
||||
label: this.main.i18nService.t('file'),
|
||||
submenu: [
|
||||
{
|
||||
label: this.main.i18nService.t('addNewLogin'),
|
||||
click: () => this.main.messagingService.send('newLogin'),
|
||||
accelerator: 'CmdOrCtrl+N',
|
||||
id: 'addNewLogin',
|
||||
},
|
||||
{
|
||||
label: this.main.i18nService.t('addNewItem'),
|
||||
id: 'addNewItem',
|
||||
submenu: [
|
||||
{
|
||||
label: this.main.i18nService.t('typeLogin'),
|
||||
click: () => this.main.messagingService.send('newLogin'),
|
||||
accelerator: 'CmdOrCtrl+Shift+L',
|
||||
},
|
||||
{
|
||||
label: this.main.i18nService.t('typeCard'),
|
||||
click: () => this.main.messagingService.send('newCard'),
|
||||
accelerator: 'CmdOrCtrl+Shift+C',
|
||||
},
|
||||
{
|
||||
label: this.main.i18nService.t('typeIdentity'),
|
||||
click: () => this.main.messagingService.send('newIdentity'),
|
||||
accelerator: 'CmdOrCtrl+Shift+I',
|
||||
},
|
||||
{
|
||||
label: this.main.i18nService.t('typeSecureNote'),
|
||||
click: () => this.main.messagingService.send('newSecureNote'),
|
||||
accelerator: 'CmdOrCtrl+Shift+S',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: this.main.i18nService.t('addNewFolder'),
|
||||
id: 'addNewFolder',
|
||||
click: () => this.main.messagingService.send('newFolder'),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: this.main.i18nService.t('syncVault'),
|
||||
id: 'syncVault',
|
||||
click: () => this.main.messagingService.send('syncVault'),
|
||||
},
|
||||
{
|
||||
label: this.main.i18nService.t('exportVault'),
|
||||
id: 'exportVault',
|
||||
click: () => this.main.messagingService.send('exportVault'),
|
||||
},
|
||||
],
|
||||
},
|
||||
this.editMenuItemOptions,
|
||||
{
|
||||
label: this.main.i18nService.t('view'),
|
||||
submenu: ([
|
||||
{
|
||||
label: this.main.i18nService.t('searchVault'),
|
||||
id: 'searchVault',
|
||||
click: () => this.main.messagingService.send('focusSearch'),
|
||||
accelerator: 'CmdOrCtrl+F',
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: this.main.i18nService.t('passwordGenerator'),
|
||||
id: 'passwordGenerator',
|
||||
click: () => this.main.messagingService.send('openPasswordGenerator'),
|
||||
accelerator: 'CmdOrCtrl+G',
|
||||
},
|
||||
{
|
||||
label: this.main.i18nService.t('passwordHistory'),
|
||||
id: 'passwordHistory',
|
||||
click: () => this.main.messagingService.send('openPasswordHistory'),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
] as MenuItemConstructorOptions[]).concat(this.viewSubMenuItemOptions),
|
||||
},
|
||||
{
|
||||
label: this.main.i18nService.t('account'),
|
||||
submenu: accountSubmenu,
|
||||
},
|
||||
this.windowMenuItemOptions,
|
||||
{
|
||||
label: this.main.i18nService.t('help'),
|
||||
role: 'help',
|
||||
submenu: helpSubmenu,
|
||||
},
|
||||
];
|
||||
|
||||
const firstMenuOptions: MenuItemConstructorOptions[] = [
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: this.main.i18nService.t(process.platform === 'darwin' ? 'preferences' : 'settings'),
|
||||
id: 'settings',
|
||||
click: () => this.main.messagingService.send('openSettings'),
|
||||
accelerator: 'CmdOrCtrl+,',
|
||||
},
|
||||
{
|
||||
label: this.main.i18nService.t('lockNow'),
|
||||
id: 'lockNow',
|
||||
click: () => this.main.messagingService.send('lockVault'),
|
||||
accelerator: 'CmdOrCtrl+L',
|
||||
},
|
||||
];
|
||||
|
||||
const updateMenuItem = {
|
||||
label: this.main.i18nService.t('checkForUpdates'),
|
||||
click: () => this.main.updaterMain.checkForUpdate(true),
|
||||
id: 'checkForUpdates',
|
||||
};
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
const firstMenuPart: MenuItemConstructorOptions[] = [
|
||||
{
|
||||
label: this.main.i18nService.t('aboutBitwarden'),
|
||||
role: 'about',
|
||||
},
|
||||
];
|
||||
|
||||
if (!isMacAppStore()) {
|
||||
firstMenuPart.push(updateMenuItem);
|
||||
}
|
||||
|
||||
template.unshift({
|
||||
label: 'Bitwarden',
|
||||
submenu: firstMenuPart.concat(firstMenuOptions, [
|
||||
{ type: 'separator' },
|
||||
], this.macAppMenuItemOptions),
|
||||
});
|
||||
|
||||
// Window menu
|
||||
template[template.length - 2].submenu = this.macWindowSubmenuOptions;
|
||||
} else {
|
||||
// File menu
|
||||
template[0].submenu = (template[0].submenu as MenuItemConstructorOptions[]).concat(
|
||||
firstMenuOptions, {
|
||||
label: this.i18nService.t('quitBitwarden'),
|
||||
role: 'quit',
|
||||
});
|
||||
|
||||
// About menu
|
||||
const aboutMenuAdditions: MenuItemConstructorOptions[] = [
|
||||
{ type: 'separator' },
|
||||
];
|
||||
|
||||
if (!isWindowsStore() && !isSnapStore()) {
|
||||
aboutMenuAdditions.push(updateMenuItem);
|
||||
}
|
||||
|
||||
aboutMenuAdditions.push({
|
||||
label: this.i18nService.t('aboutBitwarden'),
|
||||
click: async () => {
|
||||
const aboutInformation = this.i18nService.t('version', app.getVersion()) +
|
||||
'\nShell ' + process.versions.electron +
|
||||
'\nRenderer ' + process.versions.chrome +
|
||||
'\nNode ' + process.versions.node +
|
||||
'\nArchitecture ' + process.arch;
|
||||
const result = await dialog.showMessageBox(this.windowMain.win, {
|
||||
title: 'Bitwarden',
|
||||
message: 'Bitwarden',
|
||||
detail: aboutInformation,
|
||||
type: 'info',
|
||||
noLink: true,
|
||||
buttons: [this.i18nService.t('ok'), this.i18nService.t('copy')],
|
||||
});
|
||||
if (result.response === 1) {
|
||||
clipboard.writeText(aboutInformation);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
template[template.length - 1].submenu =
|
||||
(template[template.length - 1].submenu as MenuItemConstructorOptions[]).concat(aboutMenuAdditions);
|
||||
}
|
||||
|
||||
(template[template.length - 2].submenu as MenuItemConstructorOptions[]).splice(1, 0,
|
||||
{
|
||||
label: this.main.i18nService.t(process.platform === 'darwin' ? 'hideToMenuBar' : 'hideToTray'),
|
||||
click: () => this.main.messagingService.send('hideToTray'),
|
||||
accelerator: 'CmdOrCtrl+Shift+M',
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
label: this.main.i18nService.t('alwaysOnTop'),
|
||||
checked: this.windowMain.win.isAlwaysOnTop(),
|
||||
click: () => this.main.windowMain.toggleAlwaysOnTop(),
|
||||
accelerator: 'CmdOrCtrl+Shift+T',
|
||||
});
|
||||
this.menu = Menu.buildFromTemplate(template);
|
||||
Menu.setApplicationMenu(this.menu);
|
||||
private async setMenu(updateRequest?: MenuUpdateRequest) {
|
||||
Menu.setApplicationMenu(new Menubar(
|
||||
this.main.i18nService,
|
||||
this.main.messagingService,
|
||||
this.main.updaterMain,
|
||||
this.windowMain,
|
||||
await this.getWebVaultUrl(),
|
||||
app.getVersion(),
|
||||
updateRequest,
|
||||
).menu);
|
||||
}
|
||||
|
||||
private async openWebVault() {
|
||||
let webUrl = 'https://vault.bitwarden.com';
|
||||
const urlsObj: any = await this.main.storageService.get(ConstantsService.environmentUrlsKey);
|
||||
private async getWebVaultUrl() {
|
||||
let webVaultUrl = cloudWebVaultUrl;
|
||||
const urlsObj: any = await this.main.stateService.getEnvironmentUrls();
|
||||
if (urlsObj != null) {
|
||||
if (urlsObj.base != null) {
|
||||
webUrl = urlsObj.base;
|
||||
webVaultUrl = urlsObj.base;
|
||||
} else if (urlsObj.webVault != null) {
|
||||
webUrl = urlsObj.webVault;
|
||||
webVaultUrl = urlsObj.webVault;
|
||||
}
|
||||
}
|
||||
shell.openExternal(webUrl);
|
||||
return webVaultUrl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user