mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-27 12:36:14 +01: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:
parent
5865f08b37
commit
0b306ca1a7
2
jslib
2
jslib
@ -1 +1 @@
|
||||
Subproject commit 8fc3cf50d2967212ffbbf0d57cac71d0774aa2a8
|
||||
Subproject commit 512c5c283745c0e2aedaa17fc5d386c60d622064
|
@ -19,14 +19,10 @@ import { LogService } from 'jslib-common/abstractions/log.service';
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
||||
|
||||
import { LockComponent as BaseLockComponent } from 'jslib-angular/components/lock.component';
|
||||
|
||||
import { ConstantsService } from 'jslib-common/services/constants.service';
|
||||
|
||||
const BroadcasterSubscriptionId = 'LockComponent';
|
||||
|
||||
@Component({
|
||||
@ -38,20 +34,19 @@ export class LockComponent extends BaseLockComponent implements OnDestroy {
|
||||
|
||||
constructor(router: Router, i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService, messagingService: MessagingService,
|
||||
userService: UserService, cryptoService: CryptoService,
|
||||
storageService: StorageService, vaultTimeoutService: VaultTimeoutService,
|
||||
cryptoService: CryptoService, vaultTimeoutService: VaultTimeoutService,
|
||||
environmentService: EnvironmentService, stateService: StateService,
|
||||
apiService: ApiService, private route: ActivatedRoute,
|
||||
private broadcasterService: BroadcasterService, ngZone: NgZone,
|
||||
logService: LogService, keyConnectorService: KeyConnectorService) {
|
||||
super(router, i18nService, platformUtilsService, messagingService, userService, cryptoService,
|
||||
storageService, vaultTimeoutService, environmentService, stateService, apiService, logService,
|
||||
super(router, i18nService, platformUtilsService, messagingService, cryptoService,
|
||||
vaultTimeoutService, environmentService, stateService, apiService, logService,
|
||||
keyConnectorService, ngZone);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
await super.ngOnInit();
|
||||
const autoPromptBiometric = !await this.storageService.get<boolean>(ConstantsService.disableAutoBiometricsPromptKey);
|
||||
const autoPromptBiometric = !await this.stateService.getNoAutoPromptBiometrics();
|
||||
|
||||
this.route.queryParams.subscribe(params => {
|
||||
if (this.supportsBiometric && params.promptBiometric && autoPromptBiometric) {
|
||||
|
@ -1,3 +1,9 @@
|
||||
<div class="login-header">
|
||||
<a href="#" appStopClick (click)="settings()" class="environment-urls-settings-icon" attr.aria-label="{{'settings' | i18n}}">
|
||||
{{'settings' | i18n}}
|
||||
<i class="fa fa-cog fa-lg" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<form id="login-page" #form (ngSubmit)="submit()" [appApiAction]="formPromise" attr.aria-hidden="{{showingModal}}">
|
||||
<div id="content" class="content">
|
||||
<img class="logo-image" alt="Bitwarden">
|
||||
@ -47,10 +53,6 @@
|
||||
<div class="sub-options">
|
||||
<a routerLink="/hint">{{'getMasterPasswordHint' | i18n}}</a>
|
||||
</div>
|
||||
<a href="#" appStopClick (click)="settings()" class="settings-icon" attr.aria-label="{{'settings' | i18n}}">
|
||||
<i class="fa fa-cog fa-lg" aria-hidden="true"></i><span
|
||||
aria-hidden="true"> {{'settings' | i18n}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
<ng-template #environment></ng-template>
|
||||
|
@ -20,7 +20,6 @@ import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
||||
import { SyncService } from 'jslib-common/abstractions/sync.service';
|
||||
|
||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||
@ -44,11 +43,11 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy {
|
||||
syncService: SyncService, private modalService: ModalService,
|
||||
platformUtilsService: PlatformUtilsService, stateService: StateService,
|
||||
environmentService: EnvironmentService, passwordGenerationService: PasswordGenerationService,
|
||||
cryptoFunctionService: CryptoFunctionService, storageService: StorageService,
|
||||
private broadcasterService: BroadcasterService, ngZone: NgZone,
|
||||
private messagingService: MessagingService, logService: LogService) {
|
||||
cryptoFunctionService: CryptoFunctionService, private broadcasterService: BroadcasterService,
|
||||
ngZone: NgZone, private messagingService: MessagingService,
|
||||
logService: LogService) {
|
||||
super(authService, router, platformUtilsService, i18nService, stateService, environmentService,
|
||||
passwordGenerationService, cryptoFunctionService, storageService, logService, ngZone);
|
||||
passwordGenerationService, cryptoFunctionService, logService, ngZone);
|
||||
super.onSuccessfulLogin = () => {
|
||||
return syncService.fullSync(true);
|
||||
};
|
||||
|
@ -4,7 +4,7 @@ import { ApiService } from 'jslib-common/abstractions/api.service';
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
|
||||
import { PremiumComponent as BasePremiumComponent } from 'jslib-angular/components/premium.component';
|
||||
|
||||
@ -14,8 +14,8 @@ import { PremiumComponent as BasePremiumComponent } from 'jslib-angular/componen
|
||||
})
|
||||
export class PremiumComponent extends BasePremiumComponent {
|
||||
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
|
||||
apiService: ApiService, userService: UserService,
|
||||
logService: LogService) {
|
||||
super(i18nService, platformUtilsService, apiService, userService, logService);
|
||||
apiService: ApiService, logService: LogService,
|
||||
stateService: StateService) {
|
||||
super(i18nService, platformUtilsService, apiService, logService, stateService);
|
||||
}
|
||||
}
|
||||
|
@ -17,8 +17,8 @@ import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { SyncService } from 'jslib-common/abstractions/sync.service';
|
||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||
|
||||
const BroadcasterSubscriptionId = 'SetPasswordComponent';
|
||||
|
||||
@ -33,12 +33,14 @@ import {
|
||||
export class SetPasswordComponent extends BaseSetPasswordComponent implements OnDestroy {
|
||||
constructor(apiService: ApiService, i18nService: I18nService,
|
||||
cryptoService: CryptoService, messagingService: MessagingService,
|
||||
userService: UserService, passwordGenerationService: PasswordGenerationService,
|
||||
platformUtilsService: PlatformUtilsService, policyService: PolicyService, router: Router,
|
||||
passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService,
|
||||
policyService: PolicyService, router: Router,
|
||||
syncService: SyncService, route: ActivatedRoute,
|
||||
private broadcasterService: BroadcasterService, private ngZone: NgZone) {
|
||||
super(i18nService, cryptoService, messagingService, userService, passwordGenerationService,
|
||||
platformUtilsService, policyService, router, apiService, syncService, route);
|
||||
private broadcasterService: BroadcasterService, private ngZone: NgZone,
|
||||
stateService: StateService) {
|
||||
super(i18nService, cryptoService, messagingService, passwordGenerationService,
|
||||
platformUtilsService, policyService, router, apiService, syncService, route,
|
||||
stateService);
|
||||
}
|
||||
|
||||
get masterPasswordScoreWidth() {
|
||||
|
@ -13,19 +13,16 @@ import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
||||
|
||||
import { ConstantsService } from 'jslib-common/services/constants.service';
|
||||
|
||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||
|
||||
import { ElectronConstants } from 'jslib-electron/electronConstants';
|
||||
import { SetPinComponent } from '../components/set-pin.component';
|
||||
|
||||
import { Utils } from 'jslib-common/misc/utils';
|
||||
import { isWindowsStore } from 'jslib-electron/utils';
|
||||
|
||||
import { SetPinComponent } from '../components/set-pin.component';
|
||||
import { StorageLocation } from 'jslib-common/enums/storageLocation';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings',
|
||||
@ -72,9 +69,9 @@ export class SettingsComponent implements OnInit {
|
||||
vaultTimeout: FormControl = new FormControl(null);
|
||||
|
||||
constructor(private i18nService: I18nService, private platformUtilsService: PlatformUtilsService,
|
||||
private storageService: StorageService, private vaultTimeoutService: VaultTimeoutService,
|
||||
private stateService: StateService, private messagingService: MessagingService,
|
||||
private cryptoService: CryptoService, private modalService: ModalService) {
|
||||
private vaultTimeoutService: VaultTimeoutService, private stateService: StateService,
|
||||
private messagingService: MessagingService, private cryptoService: CryptoService,
|
||||
private modalService: ModalService) {
|
||||
const isMac = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
|
||||
|
||||
// Workaround to avoid ghosting trays https://github.com/electron/electron/issues/17622
|
||||
@ -153,31 +150,29 @@ export class SettingsComponent implements OnInit {
|
||||
|
||||
async ngOnInit() {
|
||||
this.showMinToTray = this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop;
|
||||
this.vaultTimeout.setValue(await this.vaultTimeoutService.getVaultTimeout());
|
||||
this.vaultTimeoutAction = await this.storageService.get<string>(ConstantsService.vaultTimeoutActionKey);
|
||||
this.vaultTimeout.setValue(await this.stateService.getVaultTimeout());
|
||||
this.vaultTimeoutAction = await this.stateService.getVaultTimeoutAction();
|
||||
const pinSet = await this.vaultTimeoutService.isPinLockSet();
|
||||
this.pin = pinSet[0] || pinSet[1];
|
||||
this.disableFavicons = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey);
|
||||
this.enableBrowserIntegration = await this.storageService.get<boolean>(
|
||||
ElectronConstants.enableBrowserIntegration);
|
||||
this.enableBrowserIntegrationFingerprint = await this.storageService.get<boolean>(ElectronConstants.enableBrowserIntegrationFingerprint);
|
||||
this.enableMinToTray = await this.storageService.get<boolean>(ElectronConstants.enableMinimizeToTrayKey);
|
||||
this.enableCloseToTray = await this.storageService.get<boolean>(ElectronConstants.enableCloseToTrayKey);
|
||||
this.enableTray = await this.storageService.get<boolean>(ElectronConstants.enableTrayKey);
|
||||
this.startToTray = await this.storageService.get<boolean>(ElectronConstants.enableStartToTrayKey);
|
||||
this.locale = await this.storageService.get<string>(ConstantsService.localeKey);
|
||||
this.theme = await this.storageService.get<string>(ConstantsService.themeKey);
|
||||
this.clearClipboard = await this.storageService.get<number>(ConstantsService.clearClipboardKey);
|
||||
this.minimizeOnCopyToClipboard = await this.storageService.get<boolean>(
|
||||
ElectronConstants.minimizeOnCopyToClipboardKey);
|
||||
this.disableFavicons = await this.stateService.getDisableFavicon();
|
||||
this.enableBrowserIntegration = await this.stateService.getEnableBrowserIntegration();
|
||||
this.enableBrowserIntegrationFingerprint = await this.stateService.getEnableBrowserIntegrationFingerprint();
|
||||
this.enableMinToTray = await this.stateService.getEnableMinimizeToTray();
|
||||
this.enableCloseToTray = await this.stateService.getEnableCloseToTray();
|
||||
this.enableTray = await this.stateService.getEnableTray();
|
||||
this.startToTray = await this.stateService.getEnableStartToTray();
|
||||
this.locale = await this.stateService.getLocale();
|
||||
this.theme = await this.stateService.getTheme();
|
||||
this.clearClipboard = await this.stateService.getClearClipboard();
|
||||
this.minimizeOnCopyToClipboard = await this.stateService.getMinimizeOnCopyToClipboard();
|
||||
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
|
||||
this.biometric = await this.vaultTimeoutService.isBiometricLockSet();
|
||||
this.biometricText = await this.storageService.get<string>(ConstantsService.biometricText);
|
||||
this.noAutoPromptBiometrics = await this.storageService.get<boolean>(ConstantsService.disableAutoBiometricsPromptKey);
|
||||
this.noAutoPromptBiometricsText = await this.storageService.get<string>(ElectronConstants.noAutoPromptBiometricsText);
|
||||
this.alwaysShowDock = await this.storageService.get<boolean>(ElectronConstants.alwaysShowDock);
|
||||
this.biometricText = await this.stateService.getBiometricText();
|
||||
this.noAutoPromptBiometrics = await this.stateService.getNoAutoPromptBiometrics();
|
||||
this.noAutoPromptBiometricsText = await this.stateService.getNoAutoPromptBiometricsText();
|
||||
this.alwaysShowDock = await this.stateService.getAlwaysShowDock();
|
||||
this.showAlwaysShowDock = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
|
||||
this.openAtLogin = await this.storageService.get<boolean>(ElectronConstants.openAtLogin);
|
||||
this.openAtLogin = await this.stateService.getOpenAtLogin();
|
||||
}
|
||||
|
||||
async saveVaultTimeoutOptions() {
|
||||
@ -233,13 +228,13 @@ export class SettingsComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
if (this.biometric) {
|
||||
await this.storageService.save(ConstantsService.biometricUnlockKey, true);
|
||||
await this.stateService.setBiometricUnlock(true);
|
||||
} else {
|
||||
await this.storageService.remove(ConstantsService.biometricUnlockKey);
|
||||
await this.storageService.remove(ConstantsService.disableAutoBiometricsPromptKey);
|
||||
await this.stateService.setBiometricUnlock(null);
|
||||
await this.stateService.setNoAutoPromptBiometrics(null);
|
||||
this.noAutoPromptBiometrics = false;
|
||||
}
|
||||
this.vaultTimeoutService.biometricLocked = false;
|
||||
await this.stateService.setBiometricLocked(false);
|
||||
await this.cryptoService.toggleKey();
|
||||
}
|
||||
|
||||
@ -249,29 +244,29 @@ export class SettingsComponent implements OnInit {
|
||||
}
|
||||
|
||||
if (this.noAutoPromptBiometrics) {
|
||||
await this.storageService.save(ConstantsService.disableAutoBiometricsPromptKey, true);
|
||||
await this.stateService.setNoAutoPromptBiometrics(true);
|
||||
} else {
|
||||
await this.storageService.remove(ConstantsService.disableAutoBiometricsPromptKey);
|
||||
await this.stateService.setNoAutoPromptBiometrics(null);
|
||||
}
|
||||
}
|
||||
|
||||
async saveFavicons() {
|
||||
await this.storageService.save(ConstantsService.disableFaviconKey, this.disableFavicons);
|
||||
await this.stateService.save(ConstantsService.disableFaviconKey, this.disableFavicons);
|
||||
await this.stateService.setDisableFavicon(this.disableFavicons);
|
||||
await this.stateService.setDisableFavicon(this.disableFavicons, { storageLocation: StorageLocation.Disk });
|
||||
this.messagingService.send('refreshCiphers');
|
||||
}
|
||||
|
||||
async saveMinToTray() {
|
||||
await this.storageService.save(ElectronConstants.enableMinimizeToTrayKey, this.enableMinToTray);
|
||||
await this.stateService.setEnableMinimizeToTray(this.enableMinToTray);
|
||||
}
|
||||
|
||||
async saveCloseToTray() {
|
||||
if (this.requireEnableTray) {
|
||||
this.enableTray = true;
|
||||
await this.storageService.save(ElectronConstants.enableTrayKey, this.enableTray);
|
||||
await this.stateService.setEnableTray(this.enableTray);
|
||||
}
|
||||
|
||||
await this.storageService.save(ElectronConstants.enableCloseToTrayKey, this.enableCloseToTray);
|
||||
await this.stateService.setEnableCloseToTray(this.enableCloseToTray);
|
||||
}
|
||||
|
||||
async saveTray() {
|
||||
@ -282,9 +277,9 @@ export class SettingsComponent implements OnInit {
|
||||
|
||||
if (confirm) {
|
||||
this.startToTray = false;
|
||||
await this.storageService.save(ElectronConstants.enableStartToTrayKey, this.startToTray);
|
||||
await this.stateService.setEnableStartToTray(this.startToTray);
|
||||
this.enableCloseToTray = false;
|
||||
await this.storageService.save(ElectronConstants.enableCloseToTrayKey, this.enableCloseToTray);
|
||||
await this.stateService.setEnableCloseToTray(this.enableCloseToTray);
|
||||
} else {
|
||||
this.enableTray = true;
|
||||
}
|
||||
@ -292,42 +287,42 @@ export class SettingsComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.storageService.save(ElectronConstants.enableTrayKey, this.enableTray);
|
||||
await this.stateService.setEnableTray(this.enableTray);
|
||||
this.messagingService.send(this.enableTray ? 'showTray' : 'removeTray');
|
||||
}
|
||||
|
||||
async saveStartToTray() {
|
||||
if (this.requireEnableTray) {
|
||||
this.enableTray = true;
|
||||
await this.storageService.save(ElectronConstants.enableTrayKey, this.enableTray);
|
||||
await this.stateService.setEnableTray(this.enableTray);
|
||||
}
|
||||
|
||||
await this.storageService.save(ElectronConstants.enableStartToTrayKey, this.startToTray);
|
||||
await this.stateService.setEnableStartToTray(this.startToTray);
|
||||
}
|
||||
|
||||
async saveLocale() {
|
||||
await this.storageService.save(ConstantsService.localeKey, this.locale);
|
||||
await this.stateService.setLocale(this.locale);
|
||||
}
|
||||
|
||||
async saveTheme() {
|
||||
await this.storageService.save(ConstantsService.themeKey, this.theme);
|
||||
await this.stateService.setTheme(this.theme);
|
||||
window.setTimeout(() => window.location.reload(), 200);
|
||||
}
|
||||
|
||||
async saveMinOnCopyToClipboard() {
|
||||
await this.storageService.save(ElectronConstants.minimizeOnCopyToClipboardKey, this.minimizeOnCopyToClipboard);
|
||||
await this.stateService.setMinimizeOnCopyToClipboard(this.minimizeOnCopyToClipboard);
|
||||
}
|
||||
|
||||
async saveClearClipboard() {
|
||||
await this.storageService.save(ConstantsService.clearClipboardKey, this.clearClipboard);
|
||||
await this.stateService.setClearClipboard(this.clearClipboard);
|
||||
}
|
||||
|
||||
async saveAlwaysShowDock() {
|
||||
await this.storageService.save(ElectronConstants.alwaysShowDock, this.alwaysShowDock);
|
||||
await this.stateService.setAlwaysShowDock(this.alwaysShowDock);
|
||||
}
|
||||
|
||||
async saveOpenAtLogin() {
|
||||
this.storageService.save(ElectronConstants.openAtLogin, this.openAtLogin);
|
||||
this.stateService.setOpenAtLogin(this.openAtLogin);
|
||||
this.messagingService.send(this.openAtLogin ? 'addOpenAtLogin' : 'removeOpenAtLogin');
|
||||
}
|
||||
|
||||
@ -350,7 +345,7 @@ export class SettingsComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.storageService.save(ElectronConstants.enableBrowserIntegration, this.enableBrowserIntegration);
|
||||
await this.stateService.setEnableBrowserIntegration(this.enableBrowserIntegration);
|
||||
this.messagingService.send(this.enableBrowserIntegration ? 'enableBrowserIntegration' : 'disableBrowserIntegration');
|
||||
|
||||
if (!this.enableBrowserIntegration) {
|
||||
@ -360,6 +355,6 @@ export class SettingsComponent implements OnInit {
|
||||
}
|
||||
|
||||
async saveBrowserIntegrationFingerprint() {
|
||||
await this.storageService.save(ElectronConstants.enableBrowserIntegrationFingerprint, this.enableBrowserIntegrationFingerprint);
|
||||
await this.stateService.setEnableBrowserIntegrationFingerprint(this.enableBrowserIntegrationFingerprint);
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import { LogService } from 'jslib-common/abstractions/log.service';
|
||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
||||
import { SyncService } from 'jslib-common/abstractions/sync.service';
|
||||
|
||||
import { SsoComponent as BaseSsoComponent } from 'jslib-angular/components/sso.component';
|
||||
@ -26,11 +25,11 @@ import { SsoComponent as BaseSsoComponent } from 'jslib-angular/components/sso.c
|
||||
export class SsoComponent extends BaseSsoComponent {
|
||||
constructor(authService: AuthService, router: Router,
|
||||
i18nService: I18nService, syncService: SyncService, route: ActivatedRoute,
|
||||
storageService: StorageService, stateService: StateService,
|
||||
platformUtilsService: PlatformUtilsService, apiService: ApiService,
|
||||
cryptoFunctionService: CryptoFunctionService, environmentService: EnvironmentService,
|
||||
passwordGenerationService: PasswordGenerationService, logService: LogService) {
|
||||
super(authService, router, i18nService, route, storageService, stateService, platformUtilsService,
|
||||
stateService: StateService, platformUtilsService: PlatformUtilsService,
|
||||
apiService: ApiService, cryptoFunctionService: CryptoFunctionService,
|
||||
environmentService: EnvironmentService, passwordGenerationService: PasswordGenerationService,
|
||||
logService: LogService) {
|
||||
super(authService, router, i18nService, route, stateService, platformUtilsService,
|
||||
apiService, cryptoFunctionService, environmentService, passwordGenerationService, logService);
|
||||
super.onSuccessfulLogin = () => {
|
||||
return syncService.fullSync(true);
|
||||
|
@ -20,7 +20,6 @@ import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
||||
import { SyncService } from 'jslib-common/abstractions/sync.service';
|
||||
|
||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||
@ -40,10 +39,10 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
||||
i18nService: I18nService, apiService: ApiService,
|
||||
platformUtilsService: PlatformUtilsService, syncService: SyncService,
|
||||
environmentService: EnvironmentService, private modalService: ModalService,
|
||||
stateService: StateService, storageService: StorageService, route: ActivatedRoute,
|
||||
stateService: StateService, route: ActivatedRoute,
|
||||
logService: LogService) {
|
||||
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService,
|
||||
stateService, storageService, route, logService);
|
||||
stateService, route, logService);
|
||||
super.onSuccessfulLogin = () => {
|
||||
return syncService.fullSync(true);
|
||||
};
|
||||
|
@ -8,8 +8,8 @@ import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { SyncService } from 'jslib-common/abstractions/sync.service';
|
||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||
|
||||
import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from 'jslib-angular/components/update-temp-password.component';
|
||||
|
||||
@ -56,10 +56,10 @@ export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent
|
||||
}
|
||||
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
|
||||
passwordGenerationService: PasswordGenerationService, policyService: PolicyService,
|
||||
cryptoService: CryptoService, userService: UserService,
|
||||
messagingService: MessagingService, apiService: ApiService,
|
||||
syncService: SyncService, logService: LogService) {
|
||||
cryptoService: CryptoService, messagingService: MessagingService,
|
||||
apiService: ApiService, syncService: SyncService,
|
||||
logService: LogService, stateService: StateService) {
|
||||
super(i18nService, platformUtilsService, passwordGenerationService, policyService, cryptoService,
|
||||
userService, messagingService, apiService, syncService, logService);
|
||||
messagingService, apiService, stateService, syncService, logService);
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
|
||||
import { AuthGuardService } from 'jslib-angular/services/auth-guard.service';
|
||||
import { LockGuardService } from 'jslib-angular/services/lock-guard.service';
|
||||
import { UnauthGuardService } from 'jslib-angular/services/unauth-guard.service';
|
||||
import { LoginGuardService } from '../services/loginGuard.service';
|
||||
|
||||
import { HintComponent } from './accounts/hint.component';
|
||||
import { LockComponent } from './accounts/lock.component';
|
||||
@ -32,8 +32,7 @@ const routes: Routes = [
|
||||
{
|
||||
path: 'login',
|
||||
component: LoginComponent,
|
||||
canActivate: [UnauthGuardService],
|
||||
|
||||
canActivate: [LoginGuardService],
|
||||
},
|
||||
{ path: '2fa', component: TwoFactorComponent },
|
||||
{ path: 'register', component: RegisterComponent },
|
||||
|
@ -36,23 +36,22 @@ import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
||||
import { SearchService } from 'jslib-common/abstractions/search.service';
|
||||
import { SettingsService } from 'jslib-common/abstractions/settings.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
||||
import { SyncService } from 'jslib-common/abstractions/sync.service';
|
||||
import { SystemService } from 'jslib-common/abstractions/system.service';
|
||||
import { TokenService } from 'jslib-common/abstractions/token.service';
|
||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
||||
|
||||
import { ConstantsService } from 'jslib-common/services/constants.service';
|
||||
|
||||
import { CipherType } from 'jslib-common/enums/cipherType';
|
||||
|
||||
import { ModalRef } from 'jslib-angular/components/modal/modal.ref';
|
||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||
import { ExportComponent } from './vault/export.component';
|
||||
import { FolderAddEditComponent } from './vault/folder-add-edit.component';
|
||||
import { PasswordGeneratorComponent } from './vault/password-generator.component';
|
||||
|
||||
import { ModalRef } from 'jslib-angular/components/modal/modal.ref';
|
||||
|
||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||
import { MenuUpdateRequest } from 'src/main/menu.updater';
|
||||
|
||||
const BroadcasterSubscriptionId = 'AppComponent';
|
||||
const IdleTimeout = 60000 * 10; // 10 minutes
|
||||
const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours
|
||||
@ -67,7 +66,8 @@ const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours
|
||||
<ng-template #appFolderAddEdit></ng-template>
|
||||
<ng-template #exportVault></ng-template>
|
||||
<ng-template #appPasswordGenerator></ng-template>
|
||||
<router-outlet></router-outlet>`,
|
||||
<app-header></app-header>
|
||||
<div id="container"><router-outlet></router-outlet></div>`,
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
@ViewChild('settings', { read: ViewContainerRef, static: true }) settingsRef: ViewContainerRef;
|
||||
@ -84,14 +84,14 @@ export class AppComponent implements OnInit {
|
||||
private idleTimer: number = null;
|
||||
private isIdle = false;
|
||||
|
||||
constructor(private broadcasterService: BroadcasterService, private userService: UserService,
|
||||
constructor(private broadcasterService: BroadcasterService,
|
||||
private tokenService: TokenService, private folderService: FolderService,
|
||||
private settingsService: SettingsService, private syncService: SyncService,
|
||||
private passwordGenerationService: PasswordGenerationService, private cipherService: CipherService,
|
||||
private authService: AuthService, private router: Router,
|
||||
private toastrService: ToastrService, private i18nService: I18nService,
|
||||
private sanitizer: DomSanitizer, private ngZone: NgZone,
|
||||
private vaultTimeoutService: VaultTimeoutService, private storageService: StorageService,
|
||||
private vaultTimeoutService: VaultTimeoutService,
|
||||
private cryptoService: CryptoService, private logService: LogService,
|
||||
private messagingService: MessagingService, private collectionService: CollectionService,
|
||||
private searchService: SearchService, private notificationsService: NotificationsService,
|
||||
@ -103,7 +103,7 @@ export class AppComponent implements OnInit {
|
||||
ngOnInit() {
|
||||
this.ngZone.runOutsideAngular(() => {
|
||||
setTimeout(async () => {
|
||||
await this.updateAppMenu('auth');
|
||||
await this.updateAppMenu();
|
||||
}, 1000);
|
||||
|
||||
window.onmousemove = () => this.recordActivity();
|
||||
@ -120,7 +120,7 @@ export class AppComponent implements OnInit {
|
||||
case 'loggedIn':
|
||||
case 'unlocked':
|
||||
this.notificationsService.updateConnection();
|
||||
this.updateAppMenu('auth');
|
||||
this.updateAppMenu();
|
||||
this.systemService.cancelProcessReload();
|
||||
break;
|
||||
case 'loggedOut':
|
||||
@ -128,7 +128,7 @@ export class AppComponent implements OnInit {
|
||||
this.modal.close();
|
||||
}
|
||||
this.notificationsService.updateConnection();
|
||||
this.updateAppMenu('auth');
|
||||
this.updateAppMenu();
|
||||
this.systemService.startProcessReload();
|
||||
await this.systemService.clearPendingClipboard();
|
||||
break;
|
||||
@ -136,21 +136,29 @@ export class AppComponent implements OnInit {
|
||||
this.router.navigate(['login']);
|
||||
break;
|
||||
case 'logout':
|
||||
this.logOut(!!message.expired);
|
||||
await this.logOut(!!message.expired, message.userId);
|
||||
break;
|
||||
case 'lockVault':
|
||||
await this.vaultTimeoutService.lock(true);
|
||||
await this.vaultTimeoutService.lock(true, message.userId);
|
||||
break;
|
||||
case 'lockAllVaults':
|
||||
for (const userId in this.stateService.accounts.getValue()) {
|
||||
if (userId != null) {
|
||||
await this.vaultTimeoutService.lock(true, userId);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'locked':
|
||||
if (this.modal != null) {
|
||||
this.modal.close();
|
||||
}
|
||||
this.stateService.purge();
|
||||
this.router.navigate(['lock']);
|
||||
this.updateAppMenu();
|
||||
if (message.userId == null || message.userId === await this.stateService.getUserId()) {
|
||||
this.router.navigate(['lock']);
|
||||
}
|
||||
this.notificationsService.updateConnection();
|
||||
this.updateAppMenu('auth');
|
||||
this.systemService.startProcessReload();
|
||||
await this.systemService.clearPendingClipboard();
|
||||
this.systemService.startProcessReload();
|
||||
break;
|
||||
case 'reloadProcess':
|
||||
window.location.reload(true);
|
||||
@ -158,7 +166,7 @@ export class AppComponent implements OnInit {
|
||||
case 'syncStarted':
|
||||
break;
|
||||
case 'syncCompleted':
|
||||
await this.updateAppMenu('sync');
|
||||
await this.updateAppMenu();
|
||||
break;
|
||||
case 'openSettings':
|
||||
await this.openModal<SettingsComponent>(SettingsComponent, this.settingsRef);
|
||||
@ -168,7 +176,7 @@ export class AppComponent implements OnInit {
|
||||
break;
|
||||
case 'showFingerprintPhrase':
|
||||
const fingerprint = await this.cryptoService.getFingerprint(
|
||||
await this.userService.getUserId());
|
||||
await this.stateService.getUserId());
|
||||
const result = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t('yourAccountsFingerprint') + ':\n' + fingerprint.join('-'),
|
||||
this.i18nService.t('fingerprintPhrase'), this.i18nService.t('learnMore'),
|
||||
@ -322,52 +330,84 @@ export class AppComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
private async updateAppMenu(type: 'auth' | 'sync') {
|
||||
let data: any;
|
||||
if (type === 'sync') {
|
||||
data = {
|
||||
hideChangeMasterPass: await this.keyConnectorService.getUsesKeyConnector(),
|
||||
private async updateAppMenu() {
|
||||
let updateRequest: MenuUpdateRequest;
|
||||
const stateAccounts = this.stateService.accounts?.getValue();
|
||||
if (stateAccounts == null || Object.keys(stateAccounts).length < 1) {
|
||||
updateRequest = {
|
||||
accounts: null,
|
||||
activeUserId: null,
|
||||
hideChangeMasterPassword: true,
|
||||
};
|
||||
} else {
|
||||
data = {
|
||||
isAuthenticated: await this.userService.isAuthenticated(),
|
||||
isLocked: await this.vaultTimeoutService.isLocked(),
|
||||
const accounts: { [userId: string]: any } = {};
|
||||
for (const i in stateAccounts) {
|
||||
if (i != null && stateAccounts[i]?.profile?.userId != null) {
|
||||
const userId = stateAccounts[i].profile.userId;
|
||||
accounts[userId] = {
|
||||
isAuthenticated: await this.stateService.getIsAuthenticated({
|
||||
userId: userId,
|
||||
}),
|
||||
isLocked: await this.vaultTimeoutService.isLocked(userId),
|
||||
email: stateAccounts[i].profile.email,
|
||||
userId: stateAccounts[i].profile.userId,
|
||||
};
|
||||
}
|
||||
}
|
||||
updateRequest = {
|
||||
accounts: accounts,
|
||||
activeUserId: await this.stateService.getUserId(),
|
||||
hideChangeMasterPassword: await this.keyConnectorService.getUsesKeyConnector(),
|
||||
};
|
||||
}
|
||||
|
||||
this.messagingService.send('updateAppMenu', data);
|
||||
this.messagingService.send('updateAppMenu', { updateRequest: updateRequest });
|
||||
}
|
||||
|
||||
private async logOut(expired: boolean) {
|
||||
await this.eventService.uploadEvents();
|
||||
const userId = await this.userService.getUserId();
|
||||
|
||||
private async logOut(expired: boolean, userId?: string) {
|
||||
await Promise.all([
|
||||
this.eventService.clearEvents(),
|
||||
this.syncService.setLastSync(new Date(0)),
|
||||
this.tokenService.clearToken(),
|
||||
this.cryptoService.clearKeys(),
|
||||
this.userService.clear(),
|
||||
this.eventService.uploadEvents(userId),
|
||||
this.syncService.setLastSync(new Date(0), userId),
|
||||
this.tokenService.clearToken(userId),
|
||||
this.cryptoService.clearKeys(userId),
|
||||
this.settingsService.clear(userId),
|
||||
this.cipherService.clear(userId),
|
||||
this.folderService.clear(userId),
|
||||
this.collectionService.clear(userId),
|
||||
this.passwordGenerationService.clear(),
|
||||
this.vaultTimeoutService.clear(),
|
||||
this.stateService.purge(),
|
||||
this.passwordGenerationService.clear(userId),
|
||||
this.vaultTimeoutService.clear(userId),
|
||||
this.policyService.clear(userId),
|
||||
this.keyConnectorService.clear(),
|
||||
]);
|
||||
|
||||
this.vaultTimeoutService.biometricLocked = true;
|
||||
this.searchService.clearIndex();
|
||||
this.authService.logOut(async () => {
|
||||
if (expired) {
|
||||
this.platformUtilsService.showToast('warning', this.i18nService.t('loggedOut'),
|
||||
this.i18nService.t('loginExpired'));
|
||||
}
|
||||
await this.stateService.setBiometricLocked(true, { userId: userId });
|
||||
|
||||
if (userId == null || userId === await this.stateService.getUserId()) {
|
||||
this.searchService.clearIndex();
|
||||
this.authService.logOut(async () => {
|
||||
if (expired) {
|
||||
this.platformUtilsService.showToast('warning', this.i18nService.t('loggedOut'),
|
||||
this.i18nService.t('loginExpired'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await this.stateService.clean({ userId: userId });
|
||||
|
||||
if (this.stateService.activeAccount.getValue() == null) {
|
||||
this.router.navigate(['login']);
|
||||
});
|
||||
} else {
|
||||
const locked = await this.vaultTimeoutService.isLocked();
|
||||
if (locked) {
|
||||
this.messagingService.send('locked');
|
||||
} else {
|
||||
this.messagingService.send('unlocked');
|
||||
this.messagingService.send('syncVault');
|
||||
this.router.navigate(['vault']);
|
||||
}
|
||||
}
|
||||
|
||||
await this.updateAppMenu();
|
||||
}
|
||||
|
||||
private async recordActivity() {
|
||||
@ -377,7 +417,7 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
|
||||
this.lastActivity = now;
|
||||
this.storageService.save(ConstantsService.lastActiveKey, now);
|
||||
await this.stateService.setLastActive(now);
|
||||
|
||||
// Idle states
|
||||
if (this.isIdle) {
|
||||
|
@ -1,16 +1,18 @@
|
||||
import 'zone.js/dist/zone';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { ServicesModule } from './services.module';
|
||||
|
||||
import { A11yModule } from '@angular/cdk/a11y';
|
||||
import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||
import { OverlayModule } from '@angular/cdk/overlay';
|
||||
import { ScrollingModule } from '@angular/cdk/scrolling';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import 'zone.js/dist/zone';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { ServicesModule } from './services.module';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
@ -29,6 +31,7 @@ import { TwoFactorComponent } from './accounts/two-factor.component';
|
||||
import { UpdateTempPasswordComponent } from './accounts/update-temp-password.component';
|
||||
import { VaultTimeoutInputComponent } from './accounts/vault-timeout-input.component';
|
||||
|
||||
import { AvatarComponent } from 'jslib-angular/components/avatar.component';
|
||||
import { CalloutComponent } from 'jslib-angular/components/callout.component';
|
||||
import { IconComponent } from 'jslib-angular/components/icon.component';
|
||||
import { BitwardenToastModule } from 'jslib-angular/components/toastr.component';
|
||||
@ -70,7 +73,10 @@ import { AddEditComponent as SendAddEditComponent } from './send/add-edit.compon
|
||||
import { EffluxDatesComponent as SendEffluxDatesComponent } from './send/efflux-dates.component';
|
||||
import { SendComponent } from './send/send.component';
|
||||
|
||||
import { AccountSwitcherComponent } from './layout/account-switcher.component';
|
||||
import { HeaderComponent } from './layout/header.component';
|
||||
import { NavComponent } from './layout/nav.component';
|
||||
import { SearchComponent } from './layout/search/search.component';
|
||||
|
||||
import { PasswordRepromptComponent } from './components/password-reprompt.component';
|
||||
import { SetPinComponent } from './components/set-pin.component';
|
||||
@ -183,6 +189,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
}),
|
||||
ScrollingModule,
|
||||
A11yModule,
|
||||
OverlayModule,
|
||||
],
|
||||
declarations: [
|
||||
A11yTitleDirective,
|
||||
@ -239,6 +246,10 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
VerifyMasterPasswordComponent,
|
||||
ViewComponent,
|
||||
ViewCustomFieldsComponent,
|
||||
HeaderComponent,
|
||||
AccountSwitcherComponent,
|
||||
AvatarComponent,
|
||||
SearchComponent,
|
||||
],
|
||||
providers: [DatePipe],
|
||||
bootstrap: [AppComponent],
|
||||
|
22
src/app/layout/account-switcher.component.html
Normal file
22
src/app/layout/account-switcher.component.html
Normal file
@ -0,0 +1,22 @@
|
||||
<a class="account-switcher" (click)="toggle()" cdkOverlayOrigin #trigger="cdkOverlayOrigin" [hidden]="!showSwitcher">
|
||||
<app-avatar [data]="activeAccountEmail" size="25" [circle]="true" [fontSize]="14" [dynamic]="true" *ngIf="activeAccountEmail != null"></app-avatar>
|
||||
<span>{{activeAccountEmail}}</span>
|
||||
<i class="fa" aria-hidden="true" [ngClass]="{'fa-chevron-down': !isOpen, 'fa-chevron-up': isOpen}"></i>
|
||||
</a>
|
||||
|
||||
<ng-template cdkConnectedOverlay [cdkConnectedOverlayOrigin]="trigger" [cdkConnectedOverlayOpen]="isOpen" cdkConnectedOverlayMinWidth="250px">
|
||||
<div class="account-switcher-dropdown" [@transformPanel]="'open'">
|
||||
<div class="accounts">
|
||||
<a *ngFor="let a of accounts | keyvalue" class="account" [ngClass]="{'active': a.value.profile.authenticationStatus == 'active'}"
|
||||
href="#" appStopClick (click)="switch(a.key)">
|
||||
<span class="email">{{a.value.profile.email}}</span>
|
||||
<span class="server">{{a.value.settings.environmentUrls.server}}</span>
|
||||
<span class="status">{{a.value.profile.authenticationStatus}}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="border"></div>
|
||||
<a class="add" routerLink="/login" (click)="toggle()">
|
||||
<i class="fa fa-plus" aria-hidden="true"></i> {{'addAccount' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
</ng-template>
|
78
src/app/layout/account-switcher.component.ts
Normal file
78
src/app/layout/account-switcher.component.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import {
|
||||
animate,
|
||||
state,
|
||||
style,
|
||||
transition,
|
||||
trigger,
|
||||
} from '@angular/animations';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
||||
|
||||
import { AuthenticationStatus } from 'jslib-common/enums/authenticationStatus';
|
||||
|
||||
import { Account } from 'jslib-common/models/domain/account';
|
||||
|
||||
@Component({
|
||||
selector: 'app-account-switcher',
|
||||
templateUrl: 'account-switcher.component.html',
|
||||
animations: [
|
||||
trigger('transformPanel', [
|
||||
state('void', style({
|
||||
opacity: 0,
|
||||
})),
|
||||
transition('void => open', animate('100ms linear', style({
|
||||
opacity: 1,
|
||||
}))),
|
||||
transition('* => void', animate('100ms linear', style({opacity: 0}))),
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class AccountSwitcherComponent implements OnInit {
|
||||
isOpen: boolean = false;
|
||||
accounts: { [userId: string]: Account } = {};
|
||||
activeAccountEmail: string;
|
||||
|
||||
get showSwitcher() {
|
||||
return this.accounts != null && Object.keys(this.accounts).length > 0;
|
||||
}
|
||||
|
||||
constructor(private stateService: StateService, private vaultTimeoutService: VaultTimeoutService,
|
||||
private messagingService: MessagingService, private router: Router) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.stateService.accounts.subscribe(async accounts => {
|
||||
for (const userId in accounts) {
|
||||
if (userId === await this.stateService.getUserId()) {
|
||||
accounts[userId].profile.authenticationStatus = AuthenticationStatus.Active;
|
||||
} else {
|
||||
accounts[userId].profile.authenticationStatus = await this.vaultTimeoutService.isLocked(userId) ?
|
||||
AuthenticationStatus.Locked :
|
||||
AuthenticationStatus.Unlocked;
|
||||
}
|
||||
}
|
||||
|
||||
this.accounts = accounts;
|
||||
this.activeAccountEmail = await this.stateService.getEmail();
|
||||
});
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this.isOpen = !this.isOpen;
|
||||
}
|
||||
|
||||
async switch(userId: string) {
|
||||
await this.stateService.setActiveUser(userId);
|
||||
const locked = await this.vaultTimeoutService.isLocked(userId);
|
||||
if (locked) {
|
||||
this.messagingService.send('locked', { userId: userId });
|
||||
} else {
|
||||
this.messagingService.send('unlocked');
|
||||
this.messagingService.send('syncVault');
|
||||
this.router.navigate(['vault']);
|
||||
}
|
||||
}
|
||||
}
|
4
src/app/layout/header.component.html
Normal file
4
src/app/layout/header.component.html
Normal file
@ -0,0 +1,4 @@
|
||||
<div class="header">
|
||||
<app-search></app-search>
|
||||
<app-account-switcher></app-account-switcher>
|
||||
</div>
|
8
src/app/layout/header.component.ts
Normal file
8
src/app/layout/header.component.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-header',
|
||||
templateUrl: 'header.component.html',
|
||||
})
|
||||
export class HeaderComponent {
|
||||
}
|
39
src/app/layout/search/search-bar.service.ts
Normal file
39
src/app/layout/search/search-bar.service.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
export type SearchBarState = {
|
||||
enabled: boolean;
|
||||
placeholderText: string;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class SearchBarService {
|
||||
|
||||
searchText = new BehaviorSubject<string>(null);
|
||||
|
||||
private _state = {
|
||||
enabled: false,
|
||||
placeholderText: '',
|
||||
};
|
||||
|
||||
// tslint:disable-next-line:member-ordering
|
||||
state = new BehaviorSubject<SearchBarState>(this._state);
|
||||
|
||||
setEnabled(enabled: boolean) {
|
||||
this._state.enabled = enabled;
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
setPlaceholderText(placeholderText: string) {
|
||||
this._state.placeholderText = placeholderText;
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
setSearchText(value: string) {
|
||||
this.searchText.next(value);
|
||||
}
|
||||
|
||||
private updateState() {
|
||||
this.state.next(this._state);
|
||||
}
|
||||
}
|
4
src/app/layout/search/search.component.html
Normal file
4
src/app/layout/search/search.component.html
Normal file
@ -0,0 +1,4 @@
|
||||
<div class="search" *ngIf="state.enabled">
|
||||
<input type="search" [placeholder]="state.placeholderText" id="search" autocomplete="off" [formControl]="searchText" appAutofocus>
|
||||
<i class="fa fa-search" aria-hidden="true"></i>
|
||||
</div>
|
24
src/app/layout/search/search.component.ts
Normal file
24
src/app/layout/search/search.component.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
|
||||
import { SearchBarService, SearchBarState } from './search-bar.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-search',
|
||||
templateUrl: 'search.component.html',
|
||||
})
|
||||
export class SearchComponent {
|
||||
|
||||
state: SearchBarState;
|
||||
searchText: FormControl = new FormControl(null);
|
||||
|
||||
constructor(private searchBarService: SearchBarService) {
|
||||
this.searchBarService.state.subscribe(state => {
|
||||
this.state = state;
|
||||
});
|
||||
|
||||
this.searchText.valueChanges.subscribe(value => {
|
||||
this.searchBarService.setSearchText(value);
|
||||
});
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@ import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
||||
import { SendService } from 'jslib-common/abstractions/send.service';
|
||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
|
||||
import { AddEditComponent as BaseAddEditComponent } from 'jslib-angular/components/send/add-edit.component';
|
||||
|
||||
@ -20,12 +20,12 @@ import { AddEditComponent as BaseAddEditComponent } from 'jslib-angular/componen
|
||||
export class AddEditComponent extends BaseAddEditComponent {
|
||||
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
|
||||
environmentService: EnvironmentService, datePipe: DatePipe,
|
||||
sendService: SendService, userService: UserService,
|
||||
sendService: SendService, stateService: StateService,
|
||||
messagingService: MessagingService, policyService: PolicyService,
|
||||
logService: LogService) {
|
||||
super(i18nService, platformUtilsService, environmentService,
|
||||
datePipe, sendService, userService, messagingService, policyService,
|
||||
logService);
|
||||
datePipe, sendService, messagingService, policyService,
|
||||
logService, stateService);
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
|
@ -3,7 +3,6 @@ import { DatePipe } from '@angular/common';
|
||||
import {
|
||||
Component,
|
||||
OnChanges,
|
||||
SimpleChanges,
|
||||
} from '@angular/core';
|
||||
|
||||
import { ControlContainer, NgForm } from '@angular/forms';
|
||||
|
@ -31,13 +31,6 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="items" class="items">
|
||||
<div class="header header-search">
|
||||
<div class="search">
|
||||
<input type="search" placeholder="{{'searchSends' | i18n}}" id="search"
|
||||
[(ngModel)]="searchText" (input)="searchTextChanged()" autocomplete="off" appAutofocus>
|
||||
<i class="fa fa-search" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="list" *ngIf="filteredSends.length">
|
||||
<a *ngFor="let s of filteredSends" appStopClick (click)="selectSend(s.id)"
|
||||
|
@ -14,7 +14,6 @@ import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.se
|
||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
||||
import { SearchService } from 'jslib-common/abstractions/search.service';
|
||||
import { SendService } from 'jslib-common/abstractions/send.service';
|
||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||
|
||||
import { SendComponent as BaseSendComponent } from 'jslib-angular/components/send/send.component';
|
||||
|
||||
@ -22,6 +21,7 @@ import { invokeMenu, RendererMenuItem } from 'jslib-electron/utils';
|
||||
|
||||
import { SendView } from 'jslib-common/models/view/sendView';
|
||||
|
||||
import { SearchBarService } from '../layout/search/search-bar.service';
|
||||
import { AddEditComponent } from './add-edit.component';
|
||||
|
||||
enum Action {
|
||||
@ -46,13 +46,20 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro
|
||||
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService,
|
||||
private broadcasterService: BroadcasterService, ngZone: NgZone,
|
||||
searchService: SearchService, policyService: PolicyService,
|
||||
userService: UserService, logService: LogService) {
|
||||
private searchBarService: SearchBarService, logService: LogService) {
|
||||
super(sendService, i18nService, platformUtilsService,
|
||||
environmentService, ngZone, searchService,
|
||||
policyService, userService, logService);
|
||||
policyService, logService);
|
||||
this.searchBarService.searchText.subscribe(searchText => {
|
||||
this.searchText = searchText;
|
||||
this.searchTextChanged();
|
||||
});
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.searchBarService.setEnabled(true);
|
||||
this.searchBarService.setPlaceholderText(this.i18nService.t('searchSends'));
|
||||
|
||||
super.ngOnInit();
|
||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
||||
this.ngZone.run(async () => {
|
||||
@ -68,6 +75,7 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro
|
||||
|
||||
ngOnDestroy() {
|
||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||
this.searchBarService.setEnabled(false);
|
||||
}
|
||||
|
||||
addSend() {
|
||||
|
@ -10,13 +10,14 @@ import { ElectronRendererSecureStorageService } from 'jslib-electron/services/el
|
||||
import { ElectronRendererStorageService } from 'jslib-electron/services/electronRendererStorage.service';
|
||||
|
||||
import { I18nService } from '../services/i18n.service';
|
||||
import { LoginGuardService } from '../services/loginGuard.service';
|
||||
import { NativeMessagingService } from '../services/nativeMessaging.service';
|
||||
import { PasswordRepromptService } from '../services/passwordReprompt.service';
|
||||
import { SearchBarService } from './layout/search/search-bar.service';
|
||||
|
||||
import { JslibServicesModule } from 'jslib-angular/services/jslib-services.module';
|
||||
|
||||
import { AuthService } from 'jslib-common/services/auth.service';
|
||||
import { ConstantsService } from 'jslib-common/services/constants.service';
|
||||
import { ContainerService } from 'jslib-common/services/container.service';
|
||||
import { EventService } from 'jslib-common/services/event.service';
|
||||
import { SystemService } from 'jslib-common/services/system.service';
|
||||
@ -46,16 +47,17 @@ import { ThemeType } from 'jslib-common/enums/themeType';
|
||||
|
||||
export function initFactory(window: Window, environmentService: EnvironmentServiceAbstraction,
|
||||
syncService: SyncServiceAbstraction, vaultTimeoutService: VaultTimeoutService,
|
||||
storageService: StorageServiceAbstraction, i18nService: I18nService, eventService: EventService,
|
||||
i18nService: I18nService, eventService: EventService,
|
||||
authService: AuthService, notificationsService: NotificationsServiceAbstraction,
|
||||
platformUtilsService: PlatformUtilsServiceAbstraction, stateService: StateServiceAbstraction,
|
||||
cryptoService: CryptoServiceAbstraction): Function {
|
||||
|
||||
return async () => {
|
||||
await stateService.init();
|
||||
await environmentService.setUrlsFromStorage();
|
||||
syncService.fullSync(true);
|
||||
vaultTimeoutService.init(true);
|
||||
const locale = await storageService.get<string>(ConstantsService.localeKey);
|
||||
await vaultTimeoutService.init(true);
|
||||
const locale = await stateService.getLocale();
|
||||
await i18nService.init(locale);
|
||||
eventService.init(true);
|
||||
authService.init();
|
||||
@ -63,22 +65,18 @@ export function initFactory(window: Window, environmentService: EnvironmentServi
|
||||
const htmlEl = window.document.documentElement;
|
||||
htmlEl.classList.add('os_' + platformUtilsService.getDeviceString());
|
||||
htmlEl.classList.add('locale_' + i18nService.translationLocale);
|
||||
|
||||
const theme = await platformUtilsService.getEffectiveTheme();
|
||||
htmlEl.classList.add('theme_' + theme);
|
||||
platformUtilsService.onDefaultSystemThemeChange(async sysTheme => {
|
||||
const bwTheme = await storageService.get<ThemeType>(ConstantsService.themeKey);
|
||||
const bwTheme = await stateService.getTheme();
|
||||
if (bwTheme == null || bwTheme === ThemeType.System) {
|
||||
htmlEl.classList.remove('theme_' + ThemeType.Light, 'theme_' + ThemeType.Dark);
|
||||
htmlEl.classList.add('theme_' + sysTheme);
|
||||
}
|
||||
});
|
||||
|
||||
stateService.save(ConstantsService.disableFaviconKey,
|
||||
await storageService.get<boolean>(ConstantsService.disableFaviconKey));
|
||||
|
||||
let installAction = null;
|
||||
const installedVersion = await storageService.get<string>(ConstantsService.installedVersionKey);
|
||||
const installedVersion = await stateService.getInstalledVersion();
|
||||
const currentVersion = await platformUtilsService.getApplicationVersion();
|
||||
if (installedVersion == null) {
|
||||
installAction = 'install';
|
||||
@ -87,7 +85,7 @@ export function initFactory(window: Window, environmentService: EnvironmentServi
|
||||
}
|
||||
|
||||
if (installAction != null) {
|
||||
await storageService.save(ConstantsService.installedVersionKey, currentVersion);
|
||||
await stateService.setInstalledVersion(currentVersion);
|
||||
}
|
||||
|
||||
const containerService = new ContainerService(cryptoService);
|
||||
@ -109,7 +107,6 @@ export function initFactory(window: Window, environmentService: EnvironmentServi
|
||||
EnvironmentServiceAbstraction,
|
||||
SyncServiceAbstraction,
|
||||
VaultTimeoutServiceAbstraction,
|
||||
StorageServiceAbstraction,
|
||||
I18nServiceAbstraction,
|
||||
EventServiceAbstraction,
|
||||
AuthServiceAbstraction,
|
||||
@ -124,12 +121,12 @@ export function initFactory(window: Window, environmentService: EnvironmentServi
|
||||
{
|
||||
provide: PlatformUtilsServiceAbstraction,
|
||||
useFactory: (i18nService: I18nServiceAbstraction, messagingService: MessagingServiceAbstraction,
|
||||
storageService: StorageServiceAbstraction) => new ElectronPlatformUtilsService(i18nService,
|
||||
messagingService, true, storageService),
|
||||
stateService: StateServiceAbstraction) => new ElectronPlatformUtilsService(i18nService,
|
||||
messagingService, true, stateService),
|
||||
deps: [
|
||||
I18nServiceAbstraction,
|
||||
MessagingServiceAbstraction,
|
||||
StorageServiceAbstraction,
|
||||
StateServiceAbstraction,
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -148,25 +145,34 @@ export function initFactory(window: Window, environmentService: EnvironmentServi
|
||||
provide: CryptoServiceAbstraction,
|
||||
useClass: ElectronCryptoService,
|
||||
deps: [
|
||||
StorageServiceAbstraction,
|
||||
'SECURE_STORAGE',
|
||||
CryptoFunctionServiceAbstraction,
|
||||
PlatformUtilsServiceAbstraction,
|
||||
LogServiceAbstraction,
|
||||
StateServiceAbstraction,
|
||||
],
|
||||
},
|
||||
{
|
||||
provide: SystemServiceAbstraction,
|
||||
useClass: SystemService,
|
||||
deps: [
|
||||
StorageServiceAbstraction,
|
||||
VaultTimeoutServiceAbstraction,
|
||||
MessagingServiceAbstraction,
|
||||
PlatformUtilsServiceAbstraction,
|
||||
StateServiceAbstraction,
|
||||
],
|
||||
},
|
||||
{ provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService },
|
||||
NativeMessagingService,
|
||||
SearchBarService,
|
||||
{
|
||||
provide: LoginGuardService,
|
||||
useClass: LoginGuardService,
|
||||
deps: [
|
||||
StateServiceAbstraction,
|
||||
PlatformUtilsServiceAbstraction,
|
||||
I18nServiceAbstraction,
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
export class ServicesModule {
|
||||
|
@ -16,15 +16,14 @@ import { FolderService } from 'jslib-common/abstractions/folder.service';
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
|
||||
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { PolicyService } from 'jslib-common/abstractions/policy.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||
|
||||
import { AddEditComponent as BaseAddEditComponent } from 'jslib-angular/components/add-edit.component';
|
||||
|
||||
|
||||
const BroadcasterSubscriptionId = 'AddEditComponent';
|
||||
|
||||
@Component({
|
||||
@ -37,13 +36,14 @@ export class AddEditComponent extends BaseAddEditComponent implements OnChanges,
|
||||
constructor(cipherService: CipherService, folderService: FolderService,
|
||||
i18nService: I18nService, platformUtilsService: PlatformUtilsService,
|
||||
auditService: AuditService, stateService: StateService,
|
||||
userService: UserService, collectionService: CollectionService,
|
||||
messagingService: MessagingService, eventService: EventService,
|
||||
policyService: PolicyService, passwordRepromptService: PasswordRepromptService,
|
||||
private broadcasterService: BroadcasterService, private ngZone: NgZone, logService: LogService) {
|
||||
collectionService: CollectionService, messagingService: MessagingService,
|
||||
eventService: EventService, policyService: PolicyService,
|
||||
passwordRepromptService: PasswordRepromptService, private broadcasterService: BroadcasterService,
|
||||
private ngZone: NgZone, logService: LogService,
|
||||
organizationService: OrganizationService) {
|
||||
super(cipherService, folderService, i18nService, platformUtilsService, auditService, stateService,
|
||||
userService, collectionService, messagingService, eventService, policyService, passwordRepromptService,
|
||||
logService);
|
||||
collectionService, messagingService, eventService, policyService, logService,
|
||||
passwordRepromptService, organizationService);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
|
@ -6,7 +6,7 @@ import { CryptoService } from 'jslib-common/abstractions/crypto.service';
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
|
||||
import { AttachmentsComponent as BaseAttachmentsComponent } from 'jslib-angular/components/attachments.component';
|
||||
|
||||
@ -16,10 +16,10 @@ import { AttachmentsComponent as BaseAttachmentsComponent } from 'jslib-angular/
|
||||
})
|
||||
export class AttachmentsComponent extends BaseAttachmentsComponent {
|
||||
constructor(cipherService: CipherService, i18nService: I18nService,
|
||||
cryptoService: CryptoService, userService: UserService,
|
||||
platformUtilsService: PlatformUtilsService, apiService: ApiService,
|
||||
logService: LogService) {
|
||||
super(cipherService, i18nService, cryptoService, userService, platformUtilsService,
|
||||
apiService, window, logService);
|
||||
cryptoService: CryptoService, platformUtilsService: PlatformUtilsService,
|
||||
apiService: ApiService, logService: LogService,
|
||||
stateService: StateService) {
|
||||
super(cipherService, i18nService, cryptoService, platformUtilsService,
|
||||
apiService, window, logService, stateService);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,3 @@
|
||||
<div class="header header-search">
|
||||
<div class="search">
|
||||
<input type="search" placeholder="{{searchPlaceholder || ('searchVault' | i18n)}}" id="search"
|
||||
[(ngModel)]="searchText" (input)="search(200)" autocomplete="off" appAutofocus>
|
||||
<i class="fa fa-search" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<cdk-virtual-scroll-viewport itemSize="42" minBufferPx="400" maxBufferPx="600" *ngIf="ciphers.length">
|
||||
<div class="list">
|
||||
|
@ -1,14 +1,27 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { CiphersComponent as BaseCiphersComponent } from 'jslib-angular/components/ciphers.component';
|
||||
import { SearchService } from 'jslib-common/abstractions/search.service';
|
||||
|
||||
import { CipherView } from 'jslib-common/models/view/cipherView';
|
||||
|
||||
import { SearchBarService } from '../layout/search/search-bar.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-vault-ciphers',
|
||||
templateUrl: 'ciphers.component.html',
|
||||
})
|
||||
export class CiphersComponent extends BaseCiphersComponent {
|
||||
|
||||
constructor(searchService: SearchService, searchBarService: SearchBarService) {
|
||||
super(searchService);
|
||||
|
||||
searchBarService.searchText.subscribe(searchText => {
|
||||
this.searchText = searchText;
|
||||
this.search(200);
|
||||
});
|
||||
}
|
||||
|
||||
trackByFn(index: number, c: CipherView) {
|
||||
return c.id;
|
||||
}
|
||||
|
@ -2,8 +2,7 @@ import { Component } from '@angular/core';
|
||||
|
||||
import { CollectionService } from 'jslib-common/abstractions/collection.service';
|
||||
import { FolderService } from 'jslib-common/abstractions/folder.service';
|
||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
|
||||
import { GroupingsComponent as BaseGroupingsComponent } from 'jslib-angular/components/groupings.component';
|
||||
|
||||
@ -13,7 +12,7 @@ import { GroupingsComponent as BaseGroupingsComponent } from 'jslib-angular/comp
|
||||
})
|
||||
export class GroupingsComponent extends BaseGroupingsComponent {
|
||||
constructor(collectionService: CollectionService, folderService: FolderService,
|
||||
storageService: StorageService, userService: UserService) {
|
||||
super(collectionService, folderService, storageService, userService);
|
||||
stateService: StateService) {
|
||||
super(collectionService, folderService, stateService);
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ import { CipherService } from 'jslib-common/abstractions/cipher.service';
|
||||
import { CollectionService } from 'jslib-common/abstractions/collection.service';
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||
import { OrganizationService } from 'jslib-common/abstractions/organization.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||
|
||||
import { ShareComponent as BaseShareComponent } from 'jslib-angular/components/share.component';
|
||||
|
||||
@ -15,9 +15,9 @@ import { ShareComponent as BaseShareComponent } from 'jslib-angular/components/s
|
||||
})
|
||||
export class ShareComponent extends BaseShareComponent {
|
||||
constructor(cipherService: CipherService, i18nService: I18nService,
|
||||
collectionService: CollectionService, userService: UserService,
|
||||
platformUtilsService: PlatformUtilsService, logService: LogService) {
|
||||
super(collectionService, platformUtilsService, i18nService, userService, cipherService,
|
||||
logService);
|
||||
collectionService: CollectionService, platformUtilsService: PlatformUtilsService,
|
||||
logService: LogService, organizationService: OrganizationService) {
|
||||
super(collectionService, platformUtilsService, i18nService, cipherService,
|
||||
logService, organizationService);
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
import { SearchBarService } from '../layout/search/search-bar.service';
|
||||
import { AddEditComponent } from './add-edit.component';
|
||||
import { AttachmentsComponent } from './attachments.component';
|
||||
import { CiphersComponent } from './ciphers.component';
|
||||
@ -35,15 +36,16 @@ import { FolderView } from 'jslib-common/models/view/folderView';
|
||||
import { ModalRef } from 'jslib-angular/components/modal/modal.ref';
|
||||
|
||||
import { ModalService } from 'jslib-angular/services/modal.service';
|
||||
|
||||
import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service';
|
||||
import { EventService } from 'jslib-common/abstractions/event.service';
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { SyncService } from 'jslib-common/abstractions/sync.service';
|
||||
import { TotpService } from 'jslib-common/abstractions/totp.service';
|
||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||
|
||||
import { invokeMenu, RendererMenuItem } from 'jslib-electron/utils';
|
||||
|
||||
@ -86,11 +88,11 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
private ngZone: NgZone, private syncService: SyncService,
|
||||
private messagingService: MessagingService,
|
||||
private platformUtilsService: PlatformUtilsService, private eventService: EventService,
|
||||
private totpService: TotpService, private userService: UserService,
|
||||
private passwordRepromptService: PasswordRepromptService) { }
|
||||
private totpService: TotpService, private passwordRepromptService: PasswordRepromptService,
|
||||
private stateService: StateService, private searchBarService: SearchBarService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
this.userHasPremiumAccess = await this.userService.canAccessPremium();
|
||||
this.userHasPremiumAccess = await this.stateService.getCanAccessPremium();
|
||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => {
|
||||
this.ngZone.run(async () => {
|
||||
let detectChanges = true;
|
||||
@ -166,9 +168,13 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
await this.load();
|
||||
}
|
||||
document.body.classList.remove('layout_frontend');
|
||||
|
||||
this.searchBarService.setEnabled(true);
|
||||
this.searchBarService.setPlaceholderText(this.i18nService.t('searchVault'));
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.searchBarService.setEnabled(false);
|
||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||
document.body.classList.add('layout_frontend');
|
||||
}
|
||||
@ -494,14 +500,14 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async clearGroupingFilters() {
|
||||
this.ciphersComponent.searchPlaceholder = this.i18nService.t('searchVault');
|
||||
this.searchBarService.setPlaceholderText(this.i18nService.t('searchVault'));
|
||||
await this.ciphersComponent.reload();
|
||||
this.clearFilters();
|
||||
this.go();
|
||||
}
|
||||
|
||||
async filterFavorites() {
|
||||
this.ciphersComponent.searchPlaceholder = this.i18nService.t('searchFavorites');
|
||||
this.searchBarService.setPlaceholderText(this.i18nService.t('searchFavorites'));
|
||||
await this.ciphersComponent.reload(c => c.favorite);
|
||||
this.clearFilters();
|
||||
this.favorites = true;
|
||||
@ -509,7 +515,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async filterDeleted() {
|
||||
this.ciphersComponent.searchPlaceholder = this.i18nService.t('searchTrash');
|
||||
this.searchBarService.setPlaceholderText(this.i18nService.t('searchTrash'));
|
||||
this.ciphersComponent.deleted = true;
|
||||
await this.ciphersComponent.reload(null, true);
|
||||
this.clearFilters();
|
||||
@ -518,7 +524,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async filterCipherType(type: CipherType) {
|
||||
this.ciphersComponent.searchPlaceholder = this.i18nService.t('searchType');
|
||||
this.searchBarService.setPlaceholderText(this.i18nService.t('searchType'));
|
||||
await this.ciphersComponent.reload(c => c.type === type);
|
||||
this.clearFilters();
|
||||
this.type = type;
|
||||
@ -527,7 +533,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
|
||||
async filterFolder(folderId: string) {
|
||||
folderId = folderId === 'none' ? null : folderId;
|
||||
this.ciphersComponent.searchPlaceholder = this.i18nService.t('searchFolder');
|
||||
this.searchBarService.setPlaceholderText(this.i18nService.t('searchFolder'));
|
||||
await this.ciphersComponent.reload(c => c.folderId === folderId);
|
||||
this.clearFilters();
|
||||
this.folderId = folderId == null ? 'none' : folderId;
|
||||
@ -535,7 +541,7 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async filterCollection(collectionId: string) {
|
||||
this.ciphersComponent.searchPlaceholder = this.i18nService.t('searchCollection');
|
||||
this.searchBarService.setPlaceholderText(this.i18nService.t('searchCollection'));
|
||||
await this.ciphersComponent.reload(c => c.collectionIds != null &&
|
||||
c.collectionIds.indexOf(collectionId) > -1);
|
||||
this.clearFilters();
|
||||
|
@ -18,9 +18,9 @@ import { LogService } from 'jslib-common/abstractions/log.service';
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { TokenService } from 'jslib-common/abstractions/token.service';
|
||||
import { TotpService } from 'jslib-common/abstractions/totp.service';
|
||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||
|
||||
import { ViewComponent as BaseViewComponent } from 'jslib-angular/components/view.component';
|
||||
|
||||
@ -40,12 +40,12 @@ export class ViewComponent extends BaseViewComponent implements OnChanges {
|
||||
cryptoService: CryptoService, platformUtilsService: PlatformUtilsService,
|
||||
auditService: AuditService, broadcasterService: BroadcasterService,
|
||||
ngZone: NgZone, changeDetectorRef: ChangeDetectorRef,
|
||||
userService: UserService, eventService: EventService, apiService: ApiService,
|
||||
eventService: EventService, apiService: ApiService,
|
||||
private messagingService: MessagingService, passwordRepromptService: PasswordRepromptService,
|
||||
logService: LogService) {
|
||||
logService: LogService, stateService: StateService) {
|
||||
super(cipherService, totpService, tokenService, i18nService, cryptoService, platformUtilsService,
|
||||
auditService, window, broadcasterService, ngZone, changeDetectorRef, userService, eventService,
|
||||
apiService, passwordRepromptService, logService);
|
||||
auditService, window, broadcasterService, ngZone, changeDetectorRef, eventService,
|
||||
apiService, passwordRepromptService, logService, stateService);
|
||||
}
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
|
@ -733,8 +733,8 @@
|
||||
"loading": {
|
||||
"message": "Loading..."
|
||||
},
|
||||
"lockNow": {
|
||||
"message": "Lock Now"
|
||||
"lockVault": {
|
||||
"message": "Lock Vault"
|
||||
},
|
||||
"passwordGenerator": {
|
||||
"message": "Password Generator"
|
||||
@ -1757,6 +1757,9 @@
|
||||
"personalVaultExportPolicyInEffect": {
|
||||
"message": "One or more organization policies prevents you from exporting your personal vault."
|
||||
},
|
||||
"addAccount": {
|
||||
"message": "Add Account"
|
||||
},
|
||||
"removeMasterPassword": {
|
||||
"message": "Remove Master Password"
|
||||
},
|
||||
@ -1783,5 +1786,11 @@
|
||||
},
|
||||
"ssoKeyConnectorUnavailable": {
|
||||
"message": "Unable to reach the key connector, try again later."
|
||||
},
|
||||
"lockAllVaults": {
|
||||
"message": "Lock All Vaults"
|
||||
},
|
||||
"accountLimitReached": {
|
||||
"message": "No more than 5 accounts may be logged in at the same time."
|
||||
}
|
||||
}
|
||||
|
43
src/main.ts
43
src/main.ts
@ -7,24 +7,27 @@ import { MenuMain } from './main/menu.main';
|
||||
import { MessagingMain } from './main/messaging.main';
|
||||
import { PowerMonitorMain } from './main/powerMonitor.main';
|
||||
|
||||
import { ConstantsService } from 'jslib-common/services/constants.service';
|
||||
|
||||
import { BiometricMain } from 'jslib-common/abstractions/biometric.main';
|
||||
import { ElectronConstants } from 'jslib-electron/electronConstants';
|
||||
|
||||
import { KeytarStorageListener } from 'jslib-electron/keytarStorageListener';
|
||||
|
||||
import { ElectronLogService } from 'jslib-electron/services/electronLog.service';
|
||||
import { ElectronMainMessagingService } from 'jslib-electron/services/electronMainMessaging.service';
|
||||
import { ElectronStorageService } from 'jslib-electron/services/electronStorage.service';
|
||||
|
||||
import { TrayMain } from 'jslib-electron/tray.main';
|
||||
import { UpdaterMain } from 'jslib-electron/updater.main';
|
||||
import { WindowMain } from 'jslib-electron/window.main';
|
||||
import { NativeMessagingMain } from './main/nativeMessaging.main';
|
||||
|
||||
import { StateService } from 'jslib-common/services/state.service';
|
||||
|
||||
export class Main {
|
||||
logService: ElectronLogService;
|
||||
i18nService: I18nService;
|
||||
storageService: ElectronStorageService;
|
||||
messagingService: ElectronMainMessagingService;
|
||||
stateService: StateService;
|
||||
keytarStorageListener: KeytarStorageListener;
|
||||
|
||||
windowMain: WindowMain;
|
||||
@ -69,23 +72,23 @@ export class Main {
|
||||
|
||||
const storageDefaults: any = {};
|
||||
// Default vault timeout to "on restart", and action to "lock"
|
||||
storageDefaults[ConstantsService.vaultTimeoutKey] = -1;
|
||||
storageDefaults[ConstantsService.vaultTimeoutActionKey] = 'lock';
|
||||
storageDefaults['global.vaultTimeout'] = -1;
|
||||
storageDefaults['global.vaultTimeoutAction'] = 'lock';
|
||||
this.storageService = new ElectronStorageService(app.getPath('userData'), storageDefaults);
|
||||
|
||||
this.windowMain = new WindowMain(this.storageService, this.logService, true, undefined, undefined,
|
||||
// TODO: this state service will have access to on disk storage, but not in memory storage.
|
||||
// If we could get this to work using the stateService singleton that the rest of the app uses we could save
|
||||
// ourselves from some hacks, like having to manually update the app menu vs. the menu subscribing to events.
|
||||
this.stateService = new StateService(this.storageService, null, this.logService, null);
|
||||
|
||||
this.windowMain = new WindowMain(this.stateService, this.logService, true, undefined, undefined,
|
||||
arg => this.processDeepLink(arg), win => this.trayMain.setupWindowListeners(win));
|
||||
this.messagingMain = new MessagingMain(this, this.storageService);
|
||||
this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain, 'desktop', () => {
|
||||
this.menuMain.updateMenuItem.enabled = false;
|
||||
}, () => {
|
||||
this.menuMain.updateMenuItem.enabled = true;
|
||||
}, () => {
|
||||
this.menuMain.updateMenuItem.label = this.i18nService.t('restartToUpdate');
|
||||
}, 'bitwarden');
|
||||
this.messagingMain = new MessagingMain(this, this.stateService);
|
||||
this.updaterMain = new UpdaterMain(this.i18nService, this.windowMain, 'desktop',
|
||||
null, null, null, 'bitwarden');
|
||||
this.menuMain = new MenuMain(this);
|
||||
this.powerMonitorMain = new PowerMonitorMain(this);
|
||||
this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.storageService);
|
||||
this.trayMain = new TrayMain(this.windowMain, this.i18nService, this.stateService);
|
||||
|
||||
this.messagingService = new ElectronMainMessagingService(this.windowMain, message => {
|
||||
this.messagingMain.onMessage(message);
|
||||
@ -94,10 +97,10 @@ export class Main {
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
const BiometricWindowsMain = require('jslib-electron/biometric.windows.main').default;
|
||||
this.biometricMain = new BiometricWindowsMain(this.storageService, this.i18nService, this.windowMain);
|
||||
this.biometricMain = new BiometricWindowsMain(this.i18nService, this.windowMain, this.stateService, this.logService);
|
||||
} else if (process.platform === 'darwin') {
|
||||
const BiometricDarwinMain = require('jslib-electron/biometric.darwin.main').default;
|
||||
this.biometricMain = new BiometricDarwinMain(this.storageService, this.i18nService);
|
||||
this.biometricMain = new BiometricDarwinMain(this.i18nService, this.stateService);
|
||||
}
|
||||
|
||||
this.keytarStorageListener = new KeytarStorageListener('Bitwarden', this.biometricMain);
|
||||
@ -108,7 +111,7 @@ export class Main {
|
||||
bootstrap() {
|
||||
this.keytarStorageListener.init();
|
||||
this.windowMain.init().then(async () => {
|
||||
const locale = await this.storageService.get<string>(ConstantsService.localeKey);
|
||||
const locale = await this.stateService.getLocale();
|
||||
await this.i18nService.init(locale != null ? locale : app.getLocale());
|
||||
this.messagingMain.init();
|
||||
this.menuMain.init();
|
||||
@ -118,7 +121,7 @@ export class Main {
|
||||
id: 'lockNow',
|
||||
click: () => this.messagingService.send('lockVault'),
|
||||
}]);
|
||||
if (await this.storageService.get<boolean>(ElectronConstants.enableStartToTrayKey)) {
|
||||
if (await this.stateService.getEnableStartToTray()) {
|
||||
this.trayMain.hideToTray();
|
||||
}
|
||||
this.powerMonitorMain.init();
|
||||
@ -127,7 +130,7 @@ export class Main {
|
||||
await this.biometricMain.init();
|
||||
}
|
||||
|
||||
if (await this.storageService.get<boolean>(ElectronConstants.enableBrowserIntegration)) {
|
||||
if (await this.stateService.getEnableBrowserIntegration()) {
|
||||
this.nativeMessagingMain.listen();
|
||||
}
|
||||
|
||||
|
96
src/main/menu.about.ts
Normal file
96
src/main/menu.about.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import {
|
||||
BrowserWindow,
|
||||
clipboard,
|
||||
dialog,
|
||||
MenuItemConstructorOptions,
|
||||
} from 'electron';
|
||||
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
|
||||
import { UpdaterMain } from 'jslib-electron/updater.main';
|
||||
import { isMac, isSnapStore, isWindowsStore } from 'jslib-electron/utils';
|
||||
|
||||
import { IMenubarMenu } from './menubar';
|
||||
|
||||
export class AboutMenu implements IMenubarMenu {
|
||||
readonly id: string = 'about';
|
||||
|
||||
get visible(): boolean {
|
||||
return !isMac();
|
||||
}
|
||||
|
||||
get label(): string {
|
||||
return this.localize('about');
|
||||
}
|
||||
|
||||
get items(): MenuItemConstructorOptions[] {
|
||||
return [
|
||||
this.separator,
|
||||
this.checkForUpdates,
|
||||
this.aboutBitwarden,
|
||||
];
|
||||
}
|
||||
|
||||
private readonly _i18nService: I18nService;
|
||||
private readonly _updater: UpdaterMain;
|
||||
private readonly _window: BrowserWindow;
|
||||
private readonly _version: string;
|
||||
|
||||
constructor(
|
||||
i18nService: I18nService,
|
||||
version: string,
|
||||
window: BrowserWindow,
|
||||
updater: UpdaterMain,
|
||||
) {
|
||||
this._i18nService = i18nService;
|
||||
this._updater = updater;
|
||||
this._version = version;
|
||||
this._window = window;
|
||||
}
|
||||
|
||||
private get separator(): MenuItemConstructorOptions {
|
||||
return { type: 'separator' };
|
||||
}
|
||||
|
||||
private get checkForUpdates(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'checkForUpdates',
|
||||
label: this.localize('checkForUpdates'),
|
||||
visible: !isWindowsStore() && !isSnapStore(),
|
||||
click: () => this.checkForUpdate(),
|
||||
};
|
||||
}
|
||||
|
||||
private get aboutBitwarden(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'aboutBitwarden',
|
||||
label: this.localize('aboutBitwarden'),
|
||||
click: async () => {
|
||||
const aboutInformation = this.localize('version', this._version) +
|
||||
'\nShell ' + process.versions.electron +
|
||||
'\nRenderer ' + process.versions.chrome +
|
||||
'\nNode ' + process.versions.node +
|
||||
'\nArchitecture ' + process.arch;
|
||||
const result = await dialog.showMessageBox(this._window, {
|
||||
title: 'Bitwarden',
|
||||
message: 'Bitwarden',
|
||||
detail: aboutInformation,
|
||||
type: 'info',
|
||||
noLink: true,
|
||||
buttons: [this.localize('ok'), this.localize('copy')],
|
||||
});
|
||||
if (result.response === 1) {
|
||||
clipboard.writeText(aboutInformation);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private localize(s: string, p?: string) {
|
||||
return this._i18nService.t(s, p);
|
||||
}
|
||||
|
||||
private async checkForUpdate() {
|
||||
this._updater.checkForUpdate(true);
|
||||
}
|
||||
}
|
121
src/main/menu.account.ts
Normal file
121
src/main/menu.account.ts
Normal file
@ -0,0 +1,121 @@
|
||||
import {
|
||||
BrowserWindow,
|
||||
dialog,
|
||||
MenuItemConstructorOptions,
|
||||
shell,
|
||||
} from 'electron';
|
||||
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
|
||||
import { isMacAppStore, isWindowsStore } from 'jslib-electron/utils';
|
||||
|
||||
import { IMenubarMenu } from './menubar';
|
||||
|
||||
export class AccountMenu implements IMenubarMenu {
|
||||
readonly id: string = 'accountMenu';
|
||||
|
||||
get label(): string {
|
||||
return this.localize('account');
|
||||
}
|
||||
|
||||
get items(): MenuItemConstructorOptions[] {
|
||||
return [
|
||||
this.premiumMembership,
|
||||
this.changeMasterPassword,
|
||||
this.twoStepLogin,
|
||||
this.fingerprintPhrase,
|
||||
];
|
||||
}
|
||||
|
||||
private readonly _i18nService: I18nService;
|
||||
private readonly _messagingService: MessagingService;
|
||||
private readonly _webVaultUrl: string;
|
||||
private readonly _window: BrowserWindow;
|
||||
private readonly _isAuthenticated: boolean;
|
||||
|
||||
constructor(
|
||||
i18nService: I18nService,
|
||||
messagingService: MessagingService,
|
||||
webVaultUrl: string,
|
||||
window: BrowserWindow,
|
||||
isAuthenticated: boolean,
|
||||
) {
|
||||
this._i18nService = i18nService;
|
||||
this._messagingService = messagingService;
|
||||
this._webVaultUrl = webVaultUrl;
|
||||
this._window = window;
|
||||
this._isAuthenticated = isAuthenticated;
|
||||
}
|
||||
|
||||
private get premiumMembership(): MenuItemConstructorOptions {
|
||||
return {
|
||||
label: this.localize('premiumMembership'),
|
||||
click: () => this.sendMessage('openPremium'),
|
||||
id: 'premiumMembership',
|
||||
visible: !isWindowsStore() && !isMacAppStore(),
|
||||
enabled: this._isAuthenticated,
|
||||
};
|
||||
}
|
||||
|
||||
private get changeMasterPassword(): MenuItemConstructorOptions {
|
||||
return {
|
||||
label: this.localize('changeMasterPass'),
|
||||
id: 'changeMasterPass',
|
||||
click: async () => {
|
||||
const result = await dialog.showMessageBox(this._window, {
|
||||
title: this.localize('changeMasterPass'),
|
||||
message: this.localize('changeMasterPass'),
|
||||
detail: this.localize('changeMasterPasswordConfirmation'),
|
||||
buttons: [this.localize('yes'), this.localize('no')],
|
||||
cancelId: 1,
|
||||
defaultId: 0,
|
||||
noLink: true,
|
||||
});
|
||||
if (result.response === 0) {
|
||||
shell.openExternal(this._webVaultUrl);
|
||||
}
|
||||
},
|
||||
enabled: this._isAuthenticated,
|
||||
};
|
||||
}
|
||||
|
||||
private get twoStepLogin(): MenuItemConstructorOptions {
|
||||
return {
|
||||
label: this.localize('twoStepLogin'),
|
||||
id: 'twoStepLogin',
|
||||
click: async () => {
|
||||
const result = await dialog.showMessageBox(this._window, {
|
||||
title: this.localize('twoStepLogin'),
|
||||
message: this.localize('twoStepLogin'),
|
||||
detail: this.localize('twoStepLoginConfirmation'),
|
||||
buttons: [this.localize('yes'), this.localize('no')],
|
||||
cancelId: 1,
|
||||
defaultId: 0,
|
||||
noLink: true,
|
||||
});
|
||||
if (result.response === 0) {
|
||||
shell.openExternal(this._webVaultUrl);
|
||||
}
|
||||
},
|
||||
enabled: this._isAuthenticated,
|
||||
};
|
||||
}
|
||||
|
||||
private get fingerprintPhrase(): MenuItemConstructorOptions {
|
||||
return {
|
||||
label: this.localize('fingerprintPhrase'),
|
||||
id: 'fingerprintPhrase',
|
||||
click: () => this.sendMessage('showFingerprintPhrase'),
|
||||
enabled: this._isAuthenticated,
|
||||
};
|
||||
}
|
||||
|
||||
private localize(s: string) {
|
||||
return this._i18nService.t(s);
|
||||
}
|
||||
|
||||
private sendMessage(message: string, args?: any) {
|
||||
this._messagingService.send(message, args);
|
||||
}
|
||||
}
|
237
src/main/menu.bitwarden.ts
Normal file
237
src/main/menu.bitwarden.ts
Normal file
@ -0,0 +1,237 @@
|
||||
import {
|
||||
BrowserWindow,
|
||||
dialog,
|
||||
MenuItem,
|
||||
MenuItemConstructorOptions,
|
||||
} from 'electron';
|
||||
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
|
||||
import { UpdaterMain } from 'jslib-electron/updater.main';
|
||||
import { isMacAppStore, isSnapStore, isWindowsStore } from 'jslib-electron/utils';
|
||||
|
||||
import { IMenubarMenu } from './menubar';
|
||||
|
||||
import { MenuAccount } from './menu.updater';
|
||||
|
||||
// AKA: "FirstMenu" or "MacMenu" - the first menu that shows on all macOs apps
|
||||
export class BitwardenMenu implements IMenubarMenu {
|
||||
readonly id: string = 'bitwarden';
|
||||
readonly label: string = 'Bitwarden';
|
||||
|
||||
get items(): MenuItemConstructorOptions[] {
|
||||
return [
|
||||
this.aboutBitwarden,
|
||||
this.checkForUpdates,
|
||||
this.separator,
|
||||
this.settings,
|
||||
this.lock,
|
||||
this.lockAll,
|
||||
this.logOut,
|
||||
this.services,
|
||||
this.separator,
|
||||
this.hideBitwarden,
|
||||
this.hideOthers,
|
||||
this.showAll,
|
||||
this.separator,
|
||||
this.quitBitwarden,
|
||||
];
|
||||
}
|
||||
|
||||
private readonly _i18nService: I18nService;
|
||||
private readonly _updater: UpdaterMain;
|
||||
private readonly _messagingService: MessagingService;
|
||||
private readonly _accounts: { [userId: string]: MenuAccount };
|
||||
private readonly _window: BrowserWindow;
|
||||
|
||||
constructor(
|
||||
i18nService: I18nService,
|
||||
messagingService: MessagingService,
|
||||
updater: UpdaterMain,
|
||||
window: BrowserWindow,
|
||||
accounts: { [userId: string]: MenuAccount },
|
||||
) {
|
||||
this._i18nService = i18nService;
|
||||
this._updater = updater;
|
||||
this._messagingService = messagingService;
|
||||
this._window = window;
|
||||
this._accounts = accounts;
|
||||
}
|
||||
|
||||
private get hasAccounts(): boolean {
|
||||
return this._accounts != null && Object.keys(this._accounts).length > 0;
|
||||
}
|
||||
|
||||
private get aboutBitwarden(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'aboutBitwarden',
|
||||
label: this.localize('aboutBitwarden'),
|
||||
role: 'about',
|
||||
visible: isMacAppStore(),
|
||||
};
|
||||
}
|
||||
|
||||
private get checkForUpdates(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'checkForUpdates',
|
||||
label: this.localize('checkForUpdates'),
|
||||
click: menuItem => this.checkForUpdate(menuItem),
|
||||
visible: !isMacAppStore() && !isWindowsStore() && !isSnapStore(),
|
||||
};
|
||||
}
|
||||
|
||||
private get separator(): MenuItemConstructorOptions {
|
||||
return {
|
||||
type: 'separator',
|
||||
};
|
||||
}
|
||||
|
||||
private get settings(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'settings',
|
||||
label: this.localize(process.platform === 'darwin' ?
|
||||
'preferences' :
|
||||
'settings'
|
||||
),
|
||||
click: () => this.sendMessage('openSettings'),
|
||||
accelerator: 'CmdOrCtrl+,',
|
||||
};
|
||||
}
|
||||
|
||||
private get lock(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'lock',
|
||||
label: this.localize('lockVault'),
|
||||
submenu: this.lockSubmenu,
|
||||
enabled: this.hasAccounts,
|
||||
};
|
||||
}
|
||||
|
||||
private get lockSubmenu(): MenuItemConstructorOptions[] {
|
||||
const value: MenuItemConstructorOptions[] = [];
|
||||
for (const userId in this._accounts) {
|
||||
if (userId == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
value.push({
|
||||
label: this._accounts[userId].email,
|
||||
id: `lockNow_${this._accounts[userId].userId}`,
|
||||
click: () => this.sendMessage('lockVault', { userId: this._accounts[userId].userId }),
|
||||
enabled: !this._accounts[userId].isLocked,
|
||||
visible: this._accounts[userId].isAuthenticated,
|
||||
});
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private get lockAll(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'lockAllNow',
|
||||
label: this.localize('lockAllVaults'),
|
||||
click: () => this.sendMessage('lockAllVaults'),
|
||||
accelerator: 'CmdOrCtrl+L',
|
||||
enabled: this.hasAccounts,
|
||||
};
|
||||
}
|
||||
|
||||
private get logOut(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'logOut',
|
||||
label: this.localize('logOut'),
|
||||
submenu: this.logOutSubmenu,
|
||||
enabled: this.hasAccounts,
|
||||
};
|
||||
}
|
||||
|
||||
private get logOutSubmenu(): MenuItemConstructorOptions[] {
|
||||
const value: MenuItemConstructorOptions[] = [];
|
||||
for (const userId in this._accounts) {
|
||||
if (userId == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
value.push({
|
||||
label: this._accounts[userId].email,
|
||||
id: `logOut_${this._accounts[userId].userId}`,
|
||||
click: async () => {
|
||||
const result = await dialog.showMessageBox(this._window, {
|
||||
title: this.localize('logOut'),
|
||||
message: this.localize('logOut'),
|
||||
detail: this.localize('logOutConfirmation'),
|
||||
buttons: [this.localize('logOut'), this.localize('cancel')],
|
||||
cancelId: 1,
|
||||
defaultId: 0,
|
||||
noLink: true,
|
||||
});
|
||||
if (result.response === 0) {
|
||||
this.sendMessage('logout', { userId: this._accounts[userId].userId });
|
||||
}
|
||||
},
|
||||
visible: this._accounts[userId].isAuthenticated,
|
||||
});
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private get services(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'services',
|
||||
label: this.localize('services'),
|
||||
role: 'services',
|
||||
submenu: [],
|
||||
visible: isMacAppStore(),
|
||||
};
|
||||
}
|
||||
|
||||
private get hideBitwarden(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'hideBitwarden',
|
||||
label: this.localize('hideBitwarden'),
|
||||
role: 'hide',
|
||||
visible: isMacAppStore(),
|
||||
};
|
||||
}
|
||||
|
||||
private get hideOthers(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'hideOthers',
|
||||
label: this.localize('hideOthers'),
|
||||
role: 'hideOthers',
|
||||
visible: isMacAppStore(),
|
||||
};
|
||||
}
|
||||
|
||||
private get showAll(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'showAll',
|
||||
label: this.localize('showAll'),
|
||||
role: 'unhide',
|
||||
visible: isMacAppStore(),
|
||||
};
|
||||
}
|
||||
|
||||
private get quitBitwarden(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'quitBitwarden',
|
||||
label: this.localize('quitBitwarden'),
|
||||
role: 'quit',
|
||||
visible: isMacAppStore(),
|
||||
};
|
||||
}
|
||||
|
||||
private localize(s: string) {
|
||||
return this._i18nService.t(s);
|
||||
}
|
||||
|
||||
private async checkForUpdate(menuItem: MenuItem) {
|
||||
menuItem.enabled = false;
|
||||
this._updater.checkForUpdate(true);
|
||||
menuItem.enabled = true;
|
||||
}
|
||||
|
||||
private sendMessage(message: string, args?: any) {
|
||||
this._messagingService.send(message, args);
|
||||
}
|
||||
}
|
134
src/main/menu.edit.ts
Normal file
134
src/main/menu.edit.ts
Normal file
@ -0,0 +1,134 @@
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
|
||||
import { IMenubarMenu } from './menubar';
|
||||
|
||||
import { MenuItemConstructorOptions } from 'electron';
|
||||
|
||||
export class EditMenu implements IMenubarMenu {
|
||||
readonly id: string = 'editMenu';
|
||||
|
||||
get label(): string {
|
||||
return this.localize('edit');
|
||||
}
|
||||
|
||||
get items(): MenuItemConstructorOptions[] {
|
||||
return [
|
||||
this.undo,
|
||||
this.redo,
|
||||
this.separator,
|
||||
this.cut,
|
||||
this.copy,
|
||||
this.paste,
|
||||
this.separator,
|
||||
this.selectAll,
|
||||
this.separator,
|
||||
this.copyUsername,
|
||||
this.copyPassword,
|
||||
this.copyVerificationCodeTotp,
|
||||
];
|
||||
}
|
||||
|
||||
private readonly _i18nService: I18nService;
|
||||
private readonly _messagingService: MessagingService;
|
||||
private readonly _isAuthenticated: boolean;
|
||||
|
||||
constructor(
|
||||
i18nService: I18nService,
|
||||
messagingService: MessagingService,
|
||||
isAuthenticated: boolean,
|
||||
) {
|
||||
this._i18nService = i18nService;
|
||||
this._messagingService = messagingService;
|
||||
this._isAuthenticated = isAuthenticated;
|
||||
}
|
||||
|
||||
private get undo(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'undo',
|
||||
label: this.localize('undo'),
|
||||
role: 'undo',
|
||||
};
|
||||
}
|
||||
|
||||
private get redo(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'redo',
|
||||
label: this.localize('redo'),
|
||||
role: 'redo',
|
||||
};
|
||||
}
|
||||
|
||||
private get separator(): MenuItemConstructorOptions {
|
||||
return { type: 'separator' };
|
||||
}
|
||||
|
||||
private get cut(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'cut',
|
||||
label: this.localize('cut'),
|
||||
role: 'cut',
|
||||
};
|
||||
}
|
||||
|
||||
private get copy(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'copy',
|
||||
label: this.localize('copy'),
|
||||
role: 'copy',
|
||||
};
|
||||
}
|
||||
|
||||
private get paste(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'paste',
|
||||
label: this.localize('paste'),
|
||||
role: 'paste',
|
||||
};
|
||||
}
|
||||
|
||||
private get selectAll(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'selectAll',
|
||||
label: this.localize('selectAll'),
|
||||
role: 'selectAll',
|
||||
};
|
||||
}
|
||||
|
||||
private get copyUsername(): MenuItemConstructorOptions {
|
||||
return {
|
||||
label: this.localize('copyUsername'),
|
||||
id: 'copyUsername',
|
||||
click: () => this.sendMessage('copyUsername'),
|
||||
accelerator: 'CmdOrCtrl+U',
|
||||
enabled: this._isAuthenticated,
|
||||
};
|
||||
}
|
||||
|
||||
private get copyPassword(): MenuItemConstructorOptions {
|
||||
return {
|
||||
label: this.localize('copyPassword'),
|
||||
id: 'copyPassword',
|
||||
click: () => this.sendMessage('copyPassword'),
|
||||
accelerator: 'CmdOrCtrl+P',
|
||||
enabled: this._isAuthenticated,
|
||||
};
|
||||
}
|
||||
|
||||
private get copyVerificationCodeTotp(): MenuItemConstructorOptions {
|
||||
return {
|
||||
label: this.localize('copyVerificationCodeTotp'),
|
||||
id: 'copyTotp',
|
||||
click: () => this.sendMessage('copyTotp'),
|
||||
accelerator: 'CmdOrCtrl+T',
|
||||
};
|
||||
}
|
||||
|
||||
private localize(s: string) {
|
||||
return this._i18nService.t(s);
|
||||
}
|
||||
|
||||
private sendMessage(message: string) {
|
||||
this._messagingService.send(message);
|
||||
}
|
||||
}
|
134
src/main/menu.file.ts
Normal file
134
src/main/menu.file.ts
Normal file
@ -0,0 +1,134 @@
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
|
||||
import { isMacAppStore } from 'jslib-electron/utils';
|
||||
|
||||
import { IMenubarMenu } from './menubar';
|
||||
|
||||
import { MenuItemConstructorOptions } from 'electron';
|
||||
|
||||
export class FileMenu implements IMenubarMenu {
|
||||
readonly id: string = 'fileMenu';
|
||||
|
||||
get label(): string {
|
||||
return this.localize('file');
|
||||
}
|
||||
|
||||
get items(): MenuItemConstructorOptions[] {
|
||||
return [
|
||||
this.addNewLogin,
|
||||
this.addNewItem,
|
||||
this.addNewFolder,
|
||||
this.separator,
|
||||
this.syncVault,
|
||||
this.exportVault,
|
||||
this.quitBitwarden,
|
||||
];
|
||||
}
|
||||
|
||||
private readonly _i18nService: I18nService;
|
||||
private readonly _messagingService: MessagingService;
|
||||
private readonly _isAuthenticated: boolean;
|
||||
|
||||
constructor(
|
||||
i18nService: I18nService,
|
||||
messagingService: MessagingService,
|
||||
isAuthenticated: boolean,
|
||||
) {
|
||||
this._i18nService = i18nService;
|
||||
this._messagingService = messagingService;
|
||||
this._isAuthenticated = isAuthenticated;
|
||||
}
|
||||
|
||||
private get addNewLogin(): MenuItemConstructorOptions {
|
||||
return {
|
||||
label: this.localize('addNewLogin'),
|
||||
click: () => this.sendMessage('newLogin'),
|
||||
accelerator: 'CmdOrCtrl+N',
|
||||
id: 'addNewLogin',
|
||||
};
|
||||
}
|
||||
|
||||
private get addNewItem(): MenuItemConstructorOptions {
|
||||
return {
|
||||
label: this.localize('addNewItem'),
|
||||
id: 'addNewItem',
|
||||
submenu: this.addNewItemSubmenu,
|
||||
enabled: this._isAuthenticated,
|
||||
};
|
||||
}
|
||||
|
||||
private get addNewItemSubmenu(): MenuItemConstructorOptions[] {
|
||||
return [
|
||||
{
|
||||
id: 'typeLogin',
|
||||
label: this.localize('typeLogin'),
|
||||
click: () => this.sendMessage('newLogin'),
|
||||
accelerator: 'CmdOrCtrl+Shift+L',
|
||||
},
|
||||
{
|
||||
id: 'typeCard',
|
||||
label: this.localize('typeCard'),
|
||||
click: () => this.sendMessage('newCard'),
|
||||
accelerator: 'CmdOrCtrl+Shift+C',
|
||||
},
|
||||
{
|
||||
id: 'typeIdentity',
|
||||
label: this.localize('typeIdentity'),
|
||||
click: () => this.sendMessage('newIdentity'),
|
||||
accelerator: 'CmdOrCtrl+Shift+I',
|
||||
},
|
||||
{
|
||||
id: 'typeSecureNote',
|
||||
label: this.localize('typeSecureNote'),
|
||||
click: () => this.sendMessage('newSecureNote'),
|
||||
accelerator: 'CmdOrCtrl+Shift+S',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private get addNewFolder(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'addNewFolder',
|
||||
label: this.localize('addNewFolder'),
|
||||
click: () => this.sendMessage('newFolder'),
|
||||
};
|
||||
}
|
||||
|
||||
private get separator(): MenuItemConstructorOptions {
|
||||
return { type: 'separator' };
|
||||
}
|
||||
|
||||
private get syncVault(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'syncVault',
|
||||
label: this.localize('syncVault'),
|
||||
click: () => this.sendMessage('syncVault'),
|
||||
};
|
||||
}
|
||||
|
||||
private get exportVault(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'exportVault',
|
||||
label: this.localize('exportVault'),
|
||||
click: () => this.sendMessage('exportVault'),
|
||||
};
|
||||
}
|
||||
|
||||
private get quitBitwarden(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'quitBitwarden',
|
||||
label: this.localize('quitBitwarden'),
|
||||
visible: !isMacAppStore(),
|
||||
role: 'quit',
|
||||
};
|
||||
}
|
||||
|
||||
private localize(s: string) {
|
||||
return this._i18nService.t(s);
|
||||
}
|
||||
|
||||
private sendMessage(message: string) {
|
||||
this._messagingService.send(message);
|
||||
}
|
||||
}
|
223
src/main/menu.help.ts
Normal file
223
src/main/menu.help.ts
Normal file
@ -0,0 +1,223 @@
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { IMenubarMenu } from './menubar';
|
||||
|
||||
import { shell } from 'electron';
|
||||
|
||||
import { isMacAppStore, isWindowsStore } from 'jslib-electron/utils';
|
||||
|
||||
import { MenuItemConstructorOptions } from 'electron';
|
||||
|
||||
export class HelpMenu implements IMenubarMenu {
|
||||
readonly id: string = 'help';
|
||||
|
||||
get label(): string {
|
||||
return this.localize('help');
|
||||
}
|
||||
|
||||
get items(): MenuItemConstructorOptions[] {
|
||||
return [
|
||||
this.emailUs,
|
||||
this.visitOurWebsite,
|
||||
this.fileBugReport,
|
||||
this.legal,
|
||||
this.separator,
|
||||
this.followUs,
|
||||
this.separator,
|
||||
this.goToWebVault,
|
||||
this.separator,
|
||||
this.getMobileApp,
|
||||
this.getBrowserExtension,
|
||||
];
|
||||
}
|
||||
|
||||
private readonly _i18nService: I18nService;
|
||||
private readonly _webVaultUrl: string;
|
||||
|
||||
constructor(
|
||||
i18nService: I18nService,
|
||||
webVaultUrl: string
|
||||
) {
|
||||
this._i18nService = i18nService;
|
||||
this._webVaultUrl = webVaultUrl;
|
||||
}
|
||||
|
||||
private get emailUs(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'emailUs',
|
||||
label: this.localize('emailUs'),
|
||||
click: () => shell.openExternal('mailTo:hello@bitwarden.com'),
|
||||
};
|
||||
}
|
||||
|
||||
private get visitOurWebsite(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'visitOurWebsite',
|
||||
label: this.localize('visitOurWebsite'),
|
||||
click: () => shell.openExternal('https://bitwarden.com/contact'),
|
||||
};
|
||||
}
|
||||
|
||||
private get fileBugReport(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'fileBugReport',
|
||||
label: this.localize('fileBugReport'),
|
||||
click: () => shell.openExternal('https://github.com/bitwarden/desktop/issues'),
|
||||
};
|
||||
}
|
||||
|
||||
private get legal(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'legal',
|
||||
label: this.localize('legal'),
|
||||
visible: !isMacAppStore(),
|
||||
submenu: this.legalSubmenu,
|
||||
};
|
||||
}
|
||||
|
||||
private get legalSubmenu(): MenuItemConstructorOptions[] {
|
||||
return [
|
||||
{
|
||||
id: 'termsOfService',
|
||||
label: this.localize('termsOfService'),
|
||||
click: () => shell.openExternal('https://bitwarden.com/terms/'),
|
||||
},
|
||||
{
|
||||
id: 'privacyPolicy',
|
||||
label: this.localize('privacyPolicy'),
|
||||
click: () => shell.openExternal('https://bitwarden.com/privacy/'),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private get separator(): MenuItemConstructorOptions {
|
||||
return { type: 'separator' };
|
||||
}
|
||||
|
||||
private get followUs(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'followUs',
|
||||
label: this.localize('followUs'),
|
||||
submenu: this.followUsSubmenu,
|
||||
};
|
||||
}
|
||||
|
||||
private get followUsSubmenu(): MenuItemConstructorOptions[] {
|
||||
return [
|
||||
{
|
||||
id: 'blog',
|
||||
label: this.localize('blog'),
|
||||
click: () => shell.openExternal('https://blog.bitwarden.com'),
|
||||
},
|
||||
{
|
||||
id: 'twitter',
|
||||
label: 'Twitter',
|
||||
click: () => shell.openExternal('https://twitter.com/bitwarden'),
|
||||
},
|
||||
{
|
||||
id: 'facebook',
|
||||
label: 'Facebook',
|
||||
click: () => shell.openExternal('https://www.facebook.com/bitwarden/'),
|
||||
},
|
||||
{
|
||||
id: 'github',
|
||||
label: 'GitHub',
|
||||
click: () => shell.openExternal('https://github.com/bitwarden'),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private get goToWebVault(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'goToWebVault',
|
||||
label: this.localize('goToWebVault'),
|
||||
click: () => shell.openExternal(this._webVaultUrl),
|
||||
};
|
||||
}
|
||||
|
||||
private get getMobileApp(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'getMobileApp',
|
||||
label: this.localize('getMobileApp'),
|
||||
visible: !isWindowsStore(),
|
||||
submenu: this.getMobileAppSubmenu,
|
||||
};
|
||||
}
|
||||
|
||||
private get getMobileAppSubmenu(): MenuItemConstructorOptions[] {
|
||||
return [
|
||||
{
|
||||
id: 'iOS',
|
||||
label: 'iOS',
|
||||
click: () => {
|
||||
shell.openExternal('https://itunes.apple.com/app/' +
|
||||
'bitwarden-free-password-manager/id1137397744?mt=8');
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'android',
|
||||
label: 'Android',
|
||||
click: () => {
|
||||
shell.openExternal('https://play.google.com/store/apps/' +
|
||||
'details?id=com.x8bit.bitwarden');
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private get getBrowserExtension(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'getBrowserExtension',
|
||||
label: this.localize('getBrowserExtension'),
|
||||
visible: !isWindowsStore(),
|
||||
submenu: this.getBrowserExtensionSubmenu,
|
||||
};
|
||||
}
|
||||
|
||||
private get getBrowserExtensionSubmenu(): MenuItemConstructorOptions[] {
|
||||
return [
|
||||
{
|
||||
id: 'chrome',
|
||||
label: 'Chrome',
|
||||
click: () => {
|
||||
shell.openExternal('https://chrome.google.com/webstore/detail/' +
|
||||
'bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb');
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'firefox',
|
||||
label: 'Firefox',
|
||||
click: () => {
|
||||
shell.openExternal('https://addons.mozilla.org/firefox/addon/' +
|
||||
'bitwarden-password-manager/');
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'firefox',
|
||||
label: 'Opera',
|
||||
click: () => {
|
||||
shell.openExternal('https://addons.opera.com/extensions/details/' +
|
||||
'bitwarden-free-password-manager/');
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'firefox',
|
||||
label: 'Edge',
|
||||
click: () => {
|
||||
shell.openExternal('https://microsoftedge.microsoft.com/addons/' +
|
||||
'detail/jbkfoedolllekgbhcbcoahefnbanhhlh');
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'safari',
|
||||
label: 'Safari',
|
||||
click: () => {
|
||||
shell.openExternal('https://bitwarden.com/download/');
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private localize(s: string) {
|
||||
return this._i18nService.t(s);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
12
src/main/menu.updater.ts
Normal file
12
src/main/menu.updater.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export class MenuUpdateRequest {
|
||||
hideChangeMasterPassword: boolean;
|
||||
activeUserId: string;
|
||||
accounts: { [userId: string]: MenuAccount };
|
||||
}
|
||||
|
||||
export class MenuAccount {
|
||||
isAuthenticated: boolean;
|
||||
isLocked: boolean;
|
||||
userId: string;
|
||||
email: string;
|
||||
}
|
140
src/main/menu.view.ts
Normal file
140
src/main/menu.view.ts
Normal file
@ -0,0 +1,140 @@
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
|
||||
import { IMenubarMenu } from './menubar';
|
||||
|
||||
import { MenuItemConstructorOptions } from 'electron';
|
||||
|
||||
export class ViewMenu implements IMenubarMenu {
|
||||
readonly id: 'viewMenu';
|
||||
|
||||
get label(): string {
|
||||
return this.localize('view');
|
||||
}
|
||||
|
||||
get items(): MenuItemConstructorOptions[] {
|
||||
return [
|
||||
this.searchVault,
|
||||
this.separator,
|
||||
this.passwordGenerator,
|
||||
this.passwordHistory,
|
||||
this.separator,
|
||||
this.zoomIn,
|
||||
this.zoomOut,
|
||||
this.resetZoom,
|
||||
this.separator,
|
||||
this.toggleFullscreen,
|
||||
this.separator,
|
||||
this.reload,
|
||||
this.toggleDevTools,
|
||||
];
|
||||
}
|
||||
|
||||
private readonly _i18nService: I18nService;
|
||||
private readonly _messagingService: MessagingService;
|
||||
private readonly _isAuthenticated: boolean;
|
||||
|
||||
constructor(
|
||||
i18nService: I18nService,
|
||||
messagingService: MessagingService,
|
||||
isAuthenticated: boolean,
|
||||
)
|
||||
{
|
||||
this._i18nService = i18nService;
|
||||
this._messagingService = messagingService;
|
||||
this._isAuthenticated = isAuthenticated;
|
||||
}
|
||||
|
||||
private get searchVault(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'searchVault',
|
||||
label: this.localize('searchVault'),
|
||||
click: () => this.sendMessage('focusSearch'),
|
||||
accelerator: 'CmdOrCtrl+F',
|
||||
enabled: this._isAuthenticated,
|
||||
};
|
||||
}
|
||||
|
||||
private get separator(): MenuItemConstructorOptions {
|
||||
return { type: 'separator' };
|
||||
}
|
||||
|
||||
private get passwordGenerator(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'passwordGenerator',
|
||||
label: this.localize('passwordGenerator'),
|
||||
click: () => this.sendMessage('openPasswordGenerator'),
|
||||
accelerator: 'CmdOrCtrl+G',
|
||||
enabled: this._isAuthenticated,
|
||||
};
|
||||
}
|
||||
|
||||
private get passwordHistory(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'passwordHistory',
|
||||
label: this.localize('passwordHistory'),
|
||||
click: () => this.sendMessage('openPasswordHistory'),
|
||||
enabled: this._isAuthenticated,
|
||||
};
|
||||
}
|
||||
|
||||
private get zoomIn(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'zoomIn',
|
||||
label: this.localize('zoomIn'),
|
||||
role: 'zoomIn',
|
||||
accelerator: 'CmdOrCtrl+=',
|
||||
};
|
||||
}
|
||||
|
||||
private get zoomOut(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'zoomOut',
|
||||
label: this.localize('zoomOut'),
|
||||
role: 'zoomOut',
|
||||
accelerator: 'CmdOrCtrl+-',
|
||||
};
|
||||
}
|
||||
|
||||
private get resetZoom(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'resetZoom',
|
||||
label: this.localize('resetZoom'),
|
||||
role: 'resetZoom',
|
||||
accelerator: 'CmdOrCtrl+0',
|
||||
};
|
||||
}
|
||||
|
||||
private get toggleFullscreen(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'toggleFullScreen',
|
||||
label: this.localize('toggleFullScreen'),
|
||||
role: 'togglefullscreen',
|
||||
};
|
||||
}
|
||||
|
||||
private get reload(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'reload',
|
||||
label: this.localize('reload'),
|
||||
role: 'forceReload',
|
||||
};
|
||||
}
|
||||
|
||||
private get toggleDevTools(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'toggleDevTools',
|
||||
label: this.localize('toggleDevTools'),
|
||||
role: 'toggleDevTools',
|
||||
accelerator: 'F12',
|
||||
};
|
||||
}
|
||||
|
||||
private localize(s: string) {
|
||||
return this._i18nService.t(s);
|
||||
}
|
||||
|
||||
private sendMessage(message: string) {
|
||||
this._messagingService.send(message);
|
||||
}
|
||||
}
|
111
src/main/menu.window.ts
Normal file
111
src/main/menu.window.ts
Normal file
@ -0,0 +1,111 @@
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
|
||||
import { isMacAppStore } from 'jslib-electron/utils';
|
||||
import { WindowMain } from 'jslib-electron/window.main';
|
||||
|
||||
import { IMenubarMenu } from './menubar';
|
||||
|
||||
import { MenuItemConstructorOptions } from 'electron';
|
||||
|
||||
export class WindowMenu implements IMenubarMenu {
|
||||
readonly id: string;
|
||||
|
||||
get label(): string {
|
||||
return this.localize('window');
|
||||
}
|
||||
|
||||
get items(): MenuItemConstructorOptions[] {
|
||||
return [
|
||||
this.minimize,
|
||||
this.hideToMenu,
|
||||
this.alwaysOnTop,
|
||||
this.zoom,
|
||||
this.separator,
|
||||
this.bringAllToFront,
|
||||
this.close,
|
||||
];
|
||||
}
|
||||
|
||||
private readonly _i18nService: I18nService;
|
||||
private readonly _messagingService: MessagingService;
|
||||
private readonly _window: WindowMain;
|
||||
|
||||
constructor(
|
||||
i18nService: I18nService,
|
||||
messagingService: MessagingService,
|
||||
windowMain: WindowMain,
|
||||
) {
|
||||
this._i18nService = i18nService;
|
||||
this._messagingService = messagingService;
|
||||
this._window = windowMain;
|
||||
}
|
||||
|
||||
private get minimize(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'minimize',
|
||||
label: this.localize('minimize'),
|
||||
role: 'minimize',
|
||||
visible: isMacAppStore(),
|
||||
};
|
||||
}
|
||||
|
||||
private get hideToMenu(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'hideToMenu',
|
||||
label: this.localize(isMacAppStore() ? 'hideToMenuBar' : 'hideToTray'),
|
||||
click: () => this.sendMessage('hideToTray'),
|
||||
accelerator: 'CmdOrCtrl+Shift+M',
|
||||
};
|
||||
}
|
||||
|
||||
private get alwaysOnTop(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'alwaysOnTop',
|
||||
label: this.localize('alwaysOnTop'),
|
||||
type: 'checkbox',
|
||||
checked: this._window.win.isAlwaysOnTop(),
|
||||
click: () => this._window.toggleAlwaysOnTop(),
|
||||
accelerator: 'CmdOrCtrl+Shift+T',
|
||||
};
|
||||
}
|
||||
|
||||
private get zoom(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'zoom',
|
||||
label: this.localize('zoom'),
|
||||
role: 'zoom',
|
||||
visible: isMacAppStore(),
|
||||
};
|
||||
}
|
||||
|
||||
private get separator(): MenuItemConstructorOptions {
|
||||
return { type: 'separator' };
|
||||
}
|
||||
|
||||
private get bringAllToFront(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'bringAllToFront',
|
||||
label: this.localize('bringAllToFront'),
|
||||
role: 'front',
|
||||
visible: isMacAppStore(),
|
||||
};
|
||||
}
|
||||
|
||||
private get close(): MenuItemConstructorOptions {
|
||||
return {
|
||||
id: 'close',
|
||||
label: this.localize('close'),
|
||||
role: 'close',
|
||||
visible: isMacAppStore(),
|
||||
};
|
||||
}
|
||||
|
||||
private localize(s: string) {
|
||||
return this._i18nService.t(s);
|
||||
}
|
||||
|
||||
private sendMessage(message: string, args?: any) {
|
||||
this._messagingService.send(message, args);
|
||||
}
|
||||
}
|
105
src/main/menubar.ts
Normal file
105
src/main/menubar.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import {
|
||||
Menu,
|
||||
MenuItemConstructorOptions,
|
||||
} from 'electron';
|
||||
|
||||
import { AboutMenu } from './menu.about';
|
||||
import { AccountMenu } from './menu.account';
|
||||
import { BitwardenMenu } from './menu.bitwarden';
|
||||
import { EditMenu } from './menu.edit';
|
||||
import { FileMenu } from './menu.file';
|
||||
import { HelpMenu } from './menu.help';
|
||||
import { MenuUpdateRequest } from './menu.updater';
|
||||
import { ViewMenu } from './menu.view';
|
||||
import { WindowMenu } from './menu.window';
|
||||
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
|
||||
import { UpdaterMain } from 'jslib-electron/updater.main';
|
||||
import { WindowMain } from 'jslib-electron/window.main';
|
||||
|
||||
export interface IMenubarMenu {
|
||||
id: string;
|
||||
label: string;
|
||||
visible?: boolean; // Assumes true if null
|
||||
items: MenuItemConstructorOptions[];
|
||||
}
|
||||
|
||||
export class Menubar {
|
||||
private readonly items: IMenubarMenu[];
|
||||
|
||||
get menu(): Menu {
|
||||
const template: MenuItemConstructorOptions[] = [];
|
||||
if (this.items != null) {
|
||||
this.items.forEach((item: IMenubarMenu) => {
|
||||
if (item != null) {
|
||||
template.push({
|
||||
id: item.id,
|
||||
label: item.label,
|
||||
submenu: item.items,
|
||||
visible: item.visible ?? true,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return Menu.buildFromTemplate(template);
|
||||
}
|
||||
|
||||
constructor(
|
||||
i18nService: I18nService,
|
||||
messagingService: MessagingService,
|
||||
updaterMain: UpdaterMain,
|
||||
windowMain: WindowMain,
|
||||
webVaultUrl: string,
|
||||
appVersion: string,
|
||||
updateRequest?: MenuUpdateRequest,
|
||||
) {
|
||||
this.items = [
|
||||
new BitwardenMenu(
|
||||
i18nService,
|
||||
messagingService,
|
||||
updaterMain,
|
||||
windowMain.win,
|
||||
updateRequest?.accounts
|
||||
),
|
||||
new FileMenu(
|
||||
i18nService,
|
||||
messagingService,
|
||||
updateRequest?.accounts[updateRequest?.activeUserId]?.isLocked ?? true,
|
||||
),
|
||||
new EditMenu(
|
||||
i18nService,
|
||||
messagingService,
|
||||
updateRequest?.accounts[updateRequest?.activeUserId]?.isLocked ?? true,
|
||||
),
|
||||
new ViewMenu(
|
||||
i18nService,
|
||||
messagingService,
|
||||
updateRequest?.accounts[updateRequest?.activeUserId]?.isLocked ?? true,
|
||||
),
|
||||
new AccountMenu(
|
||||
i18nService,
|
||||
messagingService,
|
||||
webVaultUrl,
|
||||
windowMain.win,
|
||||
updateRequest?.accounts[updateRequest?.activeUserId]?.isLocked ?? true,
|
||||
),
|
||||
new WindowMenu(
|
||||
i18nService,
|
||||
messagingService,
|
||||
windowMain,
|
||||
),
|
||||
new AboutMenu(
|
||||
i18nService,
|
||||
appVersion,
|
||||
windowMain.win,
|
||||
updaterMain,
|
||||
),
|
||||
new HelpMenu(
|
||||
i18nService,
|
||||
webVaultUrl,
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
@ -4,24 +4,24 @@ import * as path from 'path';
|
||||
|
||||
import { Main } from '../main';
|
||||
|
||||
import { ElectronConstants } from 'jslib-electron/electronConstants';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
|
||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
||||
import { MenuUpdateRequest } from './menu.updater';
|
||||
|
||||
const SyncInterval = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
export class MessagingMain {
|
||||
private syncTimeout: NodeJS.Timer;
|
||||
|
||||
constructor(private main: Main, private storageService: StorageService) { }
|
||||
constructor(private main: Main, private stateService: StateService) { }
|
||||
|
||||
init() {
|
||||
this.scheduleNextSync();
|
||||
if (process.platform === 'linux') {
|
||||
this.storageService.save(ElectronConstants.openAtLogin, fs.existsSync(this.linuxStartupFile()));
|
||||
this.stateService.setOpenAtLogin(fs.existsSync(this.linuxStartupFile()));
|
||||
} else {
|
||||
const loginSettings = app.getLoginItemSettings();
|
||||
this.storageService.save(ElectronConstants.openAtLogin, loginSettings.openAtLogin);
|
||||
this.stateService.setOpenAtLogin(loginSettings.openAtLogin);
|
||||
}
|
||||
ipcMain.on('messagingService', async (event: any, message: any) => this.onMessage(message));
|
||||
}
|
||||
@ -32,12 +32,11 @@ export class MessagingMain {
|
||||
this.scheduleNextSync();
|
||||
break;
|
||||
case 'updateAppMenu':
|
||||
this.main.menuMain.updateApplicationMenuState(message.isAuthenticated, message.isLocked,
|
||||
message.hideChangeMasterPass);
|
||||
this.updateTrayMenu(message.isAuthenticated, message.isLocked);
|
||||
this.main.menuMain.updateApplicationMenuState(message.updateRequest);
|
||||
this.updateTrayMenu(message.updateRequest);
|
||||
break;
|
||||
case 'minimizeOnCopy':
|
||||
this.storageService.get<boolean>(ElectronConstants.minimizeOnCopyToClipboardKey).then(
|
||||
this.stateService.getMinimizeOnCopyToClipboard().then(
|
||||
shouldMinimize => {
|
||||
if (shouldMinimize && this.main.windowMain.win !== null) {
|
||||
this.main.windowMain.win.minimize();
|
||||
@ -93,13 +92,14 @@ export class MessagingMain {
|
||||
}, SyncInterval);
|
||||
}
|
||||
|
||||
private updateTrayMenu(isAuthenticated: boolean, isLocked: boolean) {
|
||||
if (this.main.trayMain == null || this.main.trayMain.contextMenu == null) {
|
||||
private updateTrayMenu(updateRequest: MenuUpdateRequest) {
|
||||
if (this.main.trayMain == null || this.main.trayMain.contextMenu == null || updateRequest?.activeUserId == null) {
|
||||
return;
|
||||
}
|
||||
const lockNowTrayMenuItem = this.main.trayMain.contextMenu.getMenuItemById('lockNow');
|
||||
if (lockNowTrayMenuItem != null) {
|
||||
lockNowTrayMenuItem.enabled = isAuthenticated && !isLocked;
|
||||
const activeAccount = updateRequest.accounts[updateRequest.activeUserId];
|
||||
if (lockNowTrayMenuItem != null && activeAccount != null) {
|
||||
lockNowTrayMenuItem.enabled = activeAccount.isAuthenticated && !activeAccount.isLocked;
|
||||
}
|
||||
this.main.trayMain.updateContextMenu();
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { powerMonitor } from 'electron';
|
||||
|
||||
import { ConstantsService } from 'jslib-common/services/constants.service';
|
||||
|
||||
import { isSnapStore } from 'jslib-electron/utils';
|
||||
|
||||
import { Main } from '../main';
|
||||
@ -60,8 +58,8 @@ export class PowerMonitorMain {
|
||||
}
|
||||
|
||||
private async getVaultTimeoutOptions(): Promise<[number, string]> {
|
||||
const timeout = await this.main.storageService.get<number>(ConstantsService.vaultTimeoutKey);
|
||||
const action = await this.main.storageService.get<string>(ConstantsService.vaultTimeoutActionKey);
|
||||
const timeout = await this.main.stateService.getVaultTimeout();
|
||||
const action = await this.main.stateService.getVaultTimeoutAction();
|
||||
return [timeout, action];
|
||||
}
|
||||
}
|
||||
|
171
src/scss/header.scss
Normal file
171
src/scss/header.scss
Normal file
@ -0,0 +1,171 @@
|
||||
.header {
|
||||
-webkit-app-region: drag;
|
||||
min-height: 44px;
|
||||
max-height: 44px;
|
||||
border-bottom: 1px solid #000000;
|
||||
display: grid;
|
||||
grid-template-columns: 25% 1fr 25%;
|
||||
grid-column-gap: 5px;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
|
||||
@include themify($themes) {
|
||||
background-color: themed('headerBackgroundColor');
|
||||
border-bottom-color: themed('headerBorderColor');
|
||||
}
|
||||
|
||||
app-search {
|
||||
grid-column-start: 2;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
app-account-switcher {
|
||||
justify-self: end;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.search {
|
||||
padding: 0 7px;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
|
||||
.fa {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
left: 15px;
|
||||
|
||||
@include themify($themes) {
|
||||
color: themed('headerInputPlaceholderColor');
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
border: none;
|
||||
padding: 5px 10px 5px 30px;
|
||||
border-radius: $border-radius;
|
||||
|
||||
@include themify($themes) {
|
||||
background-color: themed('headerInputBackgroundColor');
|
||||
color: themed('headerInputColor');
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-radius: $border-radius;
|
||||
outline: none;
|
||||
|
||||
@include themify($themes) {
|
||||
background-color: themed('headerInputBackgroundFocusColor');
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-input-placeholder {
|
||||
@include themify($themes) {
|
||||
color: themed('headerInputPlaceholderColor');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.account-switcher {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
grid-column-gap: 5px;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
padding: 0 10px;
|
||||
height: 100%;
|
||||
user-select: none;
|
||||
|
||||
@include themify($themes) {
|
||||
color: themed('accountSwitcherTextColor');
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
}
|
||||
|
||||
span {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@include themify($themes) {
|
||||
background-color: themed('headerBorderColor');
|
||||
color: themed('accountSwitcherTextColor');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.account-switcher-dropdown {
|
||||
@include themify($themes) {
|
||||
background-color: themed('accountSwitcherBackgroundColor');
|
||||
}
|
||||
margin-right: 5px;
|
||||
margin-top: 1px; // Fix for border-bottom in header
|
||||
width: 100%;
|
||||
box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 3px 1px -2px rgba(0,0,0,0.12), 0 1px 5px 0 rgba(0,0,0,0.2);
|
||||
border-radius: $border-radius;
|
||||
|
||||
a {
|
||||
padding: 5px 10px;
|
||||
display: block;
|
||||
|
||||
@include themify($themes) {
|
||||
color: themed('textColor');
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@include themify($themes) {
|
||||
background-color: themed('backgroundColorAlt2');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.accounts {
|
||||
padding: 4px 0;
|
||||
|
||||
.account {
|
||||
display: grid;
|
||||
grid-column-gap: 5px;
|
||||
grid-template:
|
||||
[row1-start] "email status" [row1-end]
|
||||
[row2-start] "server server" [row2-end]
|
||||
/ 1fr auto;
|
||||
align-items: baseline;
|
||||
|
||||
.server {
|
||||
font-size: $font-size-small;
|
||||
}
|
||||
|
||||
.email {
|
||||
font-size: $font-size-large
|
||||
}
|
||||
|
||||
.status {
|
||||
font-style: italic;
|
||||
grid-area: status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.border {
|
||||
@include themify($themes) {
|
||||
background: themed('borderColor');
|
||||
}
|
||||
left: 10px;
|
||||
width: calc(100% - 20px);
|
||||
height: 1px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.add {
|
||||
margin: 4px 0;
|
||||
}
|
||||
}
|
@ -435,3 +435,7 @@ app-root > #loading {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.rounded-circle {
|
||||
border-radius:50% !important;
|
||||
}
|
||||
|
@ -8,12 +8,12 @@
|
||||
|
||||
@media (min-height: 500px) {
|
||||
height: calc(100% + 50px);
|
||||
margin-top: -50px;
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
|
||||
@media (min-height: 800px) {
|
||||
height: calc(100% + 300px);
|
||||
margin-top: -300px;
|
||||
padding-bottom: 300px;
|
||||
}
|
||||
|
||||
img {
|
||||
@ -124,6 +124,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
.login-header {
|
||||
padding: 1em;
|
||||
font-size: 1.2em;
|
||||
.environment-urls-settings-icon {
|
||||
@include themify($themes) {
|
||||
color: themed('mutedColor');
|
||||
}
|
||||
|
||||
span {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&:hover, &:focus {
|
||||
text-decoration: none;
|
||||
|
||||
@include themify($themes) {
|
||||
color: themed('primaryColor');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#sso-page {
|
||||
.content {
|
||||
width: 300px;
|
||||
|
@ -1,4 +1,5 @@
|
||||
@import "../../jslib/angular/src/scss/webfonts.css";
|
||||
@import "../../jslib/angular/src/scss/webfonts.css";
|
||||
@import '~@angular/cdk/overlay-prebuilt.css';
|
||||
@import "variables.scss";
|
||||
@import "base.scss";
|
||||
@import "grid.scss";
|
||||
@ -11,3 +12,4 @@
|
||||
@import "modal.scss";
|
||||
@import "plugins.scss";
|
||||
@import "environment.scss";
|
||||
@import "header.scss";
|
||||
|
@ -88,6 +88,8 @@ $themes: (
|
||||
passwordSpecialColor: #c40800,
|
||||
calloutBorderColor: $border-color-dark,
|
||||
calloutBackgroundColor: $background-color,
|
||||
acccountSwitcherBackgroundColor: $background-color,
|
||||
accountSwitcherTextColor: #ffffff,
|
||||
),
|
||||
dark: (
|
||||
textColor: #ffffff,
|
||||
@ -138,6 +140,8 @@ $themes: (
|
||||
passwordSpecialColor: #ff7c70,
|
||||
calloutBorderColor: #2f2f2f,
|
||||
calloutBackgroundColor: #363636,
|
||||
accountSwitcherBackgroundColor: #2f2f2f,
|
||||
accountSwitcherTextColor: #ffffff,
|
||||
),
|
||||
nord: (
|
||||
textColor: $nord5,
|
||||
@ -188,6 +192,8 @@ $themes: (
|
||||
passwordSpecialColor: $nord12,
|
||||
calloutBorderColor: $nord1,
|
||||
calloutBackgroundColor: $nord2,
|
||||
accountSwitcherBackgroundColor: $nord0,
|
||||
accountSwitcherTextColor: $nord5,
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -1,5 +1,16 @@
|
||||
@import "variables.scss";
|
||||
|
||||
app-root {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#container {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.vault {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
@ -356,67 +367,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
min-height: 44px;
|
||||
max-height: 44px;
|
||||
flex: 0 0 auto;
|
||||
border-bottom: 1px solid #000000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@include themify($themes) {
|
||||
background-color: themed('headerBackgroundColor');
|
||||
border-bottom-color: themed('headerBorderColor');
|
||||
}
|
||||
|
||||
&.header-search {
|
||||
.search {
|
||||
padding: 0 7px;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
|
||||
.fa {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
left: 15px;
|
||||
|
||||
@include themify($themes) {
|
||||
color: themed('headerInputPlaceholderColor');
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
border: none;
|
||||
padding: 5px 10px 5px 30px;
|
||||
border-radius: $border-radius;
|
||||
|
||||
@include themify($themes) {
|
||||
background-color: themed('headerInputBackgroundColor');
|
||||
color: themed('headerInputColor');
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-radius: $border-radius;
|
||||
outline: none;
|
||||
|
||||
@include themify($themes) {
|
||||
background-color: themed('headerInputBackgroundFocusColor');
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-input-placeholder {
|
||||
@include themify($themes) {
|
||||
color: themed('headerInputPlaceholderColor');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1 1 auto;
|
||||
position: relative;
|
||||
|
25
src/services/loginGuard.service.ts
Normal file
25
src/services/loginGuard.service.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CanActivate } from '@angular/router';
|
||||
|
||||
import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
|
||||
const maxAllowedAccounts = 5;
|
||||
|
||||
@Injectable()
|
||||
export class LoginGuardService implements CanActivate {
|
||||
protected homepage = 'vault';
|
||||
constructor(private stateService: StateService, private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService) { }
|
||||
|
||||
async canActivate() {
|
||||
const accounts = this.stateService.accounts.getValue();
|
||||
if (accounts != null && Object.keys(accounts).length >= maxAllowedAccounts) {
|
||||
this.platformUtilsService.showToast('error', null, this.i18nService.t('accountLimitReached'));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -8,13 +8,14 @@ import { I18nService } from 'jslib-common/abstractions/i18n.service';
|
||||
import { LogService } from 'jslib-common/abstractions/log.service';
|
||||
import { MessagingService } from 'jslib-common/abstractions/messaging.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
import { StorageService } from 'jslib-common/abstractions/storage.service';
|
||||
import { UserService } from 'jslib-common/abstractions/user.service';
|
||||
import { StateService } from 'jslib-common/abstractions/state.service';
|
||||
import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service';
|
||||
|
||||
import { Utils } from 'jslib-common/misc/utils';
|
||||
|
||||
import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey';
|
||||
import { ElectronConstants } from 'jslib-electron/electronConstants';
|
||||
|
||||
import { KeySuffixOptions } from 'jslib-common/enums/keySuffixOptions';
|
||||
|
||||
const MessageValidTimeout = 10 * 1000;
|
||||
const EncryptionAlgorithm = 'sha1';
|
||||
@ -25,9 +26,9 @@ export class NativeMessagingService {
|
||||
|
||||
constructor(private cryptoFunctionService: CryptoFunctionService, private cryptoService: CryptoService,
|
||||
private platformUtilService: PlatformUtilsService, private logService: LogService,
|
||||
private i18nService: I18nService, private userService: UserService, private messagingService: MessagingService,
|
||||
private vaultTimeoutService: VaultTimeoutService, private storageService: StorageService) {
|
||||
ipcRenderer.on('nativeMessaging', async (event: any, message: any) => {
|
||||
private i18nService: I18nService, private messagingService: MessagingService,
|
||||
private vaultTimeoutService: VaultTimeoutService, private stateService: StateService) {
|
||||
ipcRenderer.on('nativeMessaging', async (_event: any, message: any) => {
|
||||
this.messageHandler(message);
|
||||
});
|
||||
}
|
||||
@ -41,15 +42,15 @@ export class NativeMessagingService {
|
||||
const remotePublicKey = Utils.fromB64ToArray(rawMessage.publicKey).buffer;
|
||||
|
||||
// Valudate the UserId to ensure we are logged into the same account.
|
||||
if (rawMessage.userId !== await this.userService.getUserId()) {
|
||||
if (rawMessage.userId !== await this.stateService.getUserId()) {
|
||||
ipcRenderer.send('nativeMessagingReply', {command: 'wrongUserId', appId: appId});
|
||||
return;
|
||||
}
|
||||
|
||||
if (await this.storageService.get<boolean>(ElectronConstants.enableBrowserIntegrationFingerprint)) {
|
||||
if (await this.stateService.getEnableBrowserIntegrationFingerprint()) {
|
||||
ipcRenderer.send('nativeMessagingReply', {command: 'verifyFingerprint', appId: appId});
|
||||
|
||||
const fingerprint = (await this.cryptoService.getFingerprint(await this.userService.getUserId(), remotePublicKey)).join(' ');
|
||||
const fingerprint = (await this.cryptoService.getFingerprint(await this.stateService.getUserId(), remotePublicKey)).join(' ');
|
||||
|
||||
this.messagingService.send('setFocus');
|
||||
|
||||
@ -104,7 +105,7 @@ export class NativeMessagingService {
|
||||
});
|
||||
}
|
||||
|
||||
const keyB64 = await (await this.cryptoService.getKeyFromStorage('biometric')).keyB64;
|
||||
const keyB64 = (await this.cryptoService.getKeyFromStorage(KeySuffixOptions.Biometric)).keyB64;
|
||||
|
||||
if (keyB64 != null) {
|
||||
this.send({ command: 'biometricUnlock', response: 'unlocked', keyB64: keyB64 }, appId);
|
||||
|
Loading…
Reference in New Issue
Block a user