diff --git a/jslib b/jslib index ee164bebc6..7941664a59 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit ee164bebc65aa56e41a122eb4ece8971eb23119b +Subproject commit 7941664a59f90a1b510b13d37062b90210da3b3c diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 2b1fdafc72..93a13e4f1c 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -2,11 +2,9 @@ import { BodyOutputType, Toast, ToasterConfig, - ToasterContainerComponent, ToasterService, } from 'angular2-toaster'; import { Angulartics2 } from 'angulartics2'; -import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; import { Component, @@ -52,10 +50,16 @@ import { UserService } from 'jslib/abstractions/user.service'; import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service'; import { ConstantsService } from 'jslib/services/constants.service'; -import { NativeMessagingService } from '../services/nativeMessaging.service'; + +import { CipherType } from 'jslib/enums/cipherType'; + +import { ExportComponent } from './vault/export.component'; +import { FolderAddEditComponent } from './vault/folder-add-edit.component'; +import { PasswordGeneratorComponent } from './vault/password-generator.component'; const BroadcasterSubscriptionId = 'AppComponent'; const IdleTimeout = 60000 * 10; // 10 minutes +const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours @Component({ selector: 'app-root', @@ -65,12 +69,20 @@ const IdleTimeout = 60000 * 10; // 10 minutes + + + `, }) export class AppComponent implements OnInit { @ViewChild('settings', { read: ViewContainerRef, static: true }) settingsRef: ViewContainerRef; @ViewChild('premium', { read: ViewContainerRef, static: true }) premiumRef: ViewContainerRef; @ViewChild('passwordHistory', { read: ViewContainerRef, static: true }) passwordHistoryRef: ViewContainerRef; + @ViewChild('exportVault', { read: ViewContainerRef, static: true }) exportVaultModalRef: ViewContainerRef; + @ViewChild('appFolderAddEdit', { read: ViewContainerRef, static: true }) + folderAddEditModalRef: ViewContainerRef; + @ViewChild('appPasswordGenerator', { read: ViewContainerRef, static: true }) + passwordGeneratorModalRef: ViewContainerRef; toasterConfig: ToasterConfig = new ToasterConfig({ showCloseButton: true, @@ -84,8 +96,7 @@ export class AppComponent implements OnInit { private idleTimer: number = null; private isIdle = false; - constructor(private angulartics2GoogleAnalytics: Angulartics2GoogleAnalytics, - private broadcasterService: BroadcasterService, private userService: UserService, + constructor(private broadcasterService: BroadcasterService, private userService: UserService, private tokenService: TokenService, private folderService: FolderService, private settingsService: SettingsService, private syncService: SyncService, private passwordGenerationService: PasswordGenerationService, private cipherService: CipherService, @@ -98,7 +109,7 @@ export class AppComponent implements OnInit { private searchService: SearchService, private notificationsService: NotificationsService, private platformUtilsService: PlatformUtilsService, private systemService: SystemService, private stateService: StateService, private eventService: EventService, - private policyService: PolicyService, private nativeMessagingService: NativeMessagingService) { } + private policyService: PolicyService) { } ngOnInit() { this.ngZone.runOutsideAngular(() => { @@ -173,7 +184,7 @@ export class AppComponent implements OnInit { this.i18nService.t('fingerprintPhrase'), this.i18nService.t('learnMore'), this.i18nService.t('close')); if (result) { - this.platformUtilsService.launchUri( + this.platformUtilsService.launchUri( 'https://help.bitwarden.com/article/fingerprint-phrase/'); } break; @@ -197,7 +208,63 @@ export class AppComponent implements OnInit { break; case 'ssoCallback': this.router.navigate(['sso'], { queryParams: { code: message.code, state: message.state } }); + case 'premiumRequired': + const premiumConfirmed = await this.platformUtilsService.showDialog( + this.i18nService.t('premiumRequiredDesc'), this.i18nService.t('premiumRequired'), + this.i18nService.t('learnMore'), this.i18nService.t('cancel')); + if (premiumConfirmed) { + this.openModal(PremiumComponent, this.premiumRef); + } + break; + case 'syncVault': + try { + await this.syncService.fullSync(true, true); + this.toasterService.popAsync('success', null, this.i18nService.t('syncingComplete')); + this.analytics.eventTrack.next({ action: 'Synced Full' }); + } catch { + this.toasterService.popAsync('error', null, this.i18nService.t('syncingFailed')); + } + break; + case 'checkSyncVault': + try { + const lastSync = await this.syncService.getLastSync(); + let lastSyncAgo = SyncInterval + 1; + if (lastSync != null) { + lastSyncAgo = new Date().getTime() - lastSync.getTime(); + } + + if (lastSyncAgo >= SyncInterval) { + await this.syncService.fullSync(false); + } + } catch { } + this.messagingService.send('scheduleNextSync'); + break; + case 'exportVault': + await this.openExportVault(); + break; + case 'newLogin': + this.routeToVault('add', CipherType.Login); + break; + case 'newCard': + this.routeToVault('add', CipherType.Card); + break; + case 'newIdentity': + this.routeToVault('add', CipherType.Identity); + break; + case 'newSecureNote': + this.routeToVault('add', CipherType.SecureNote); + break; default: + break; + case 'newFolder': + await this.addFolder(); + break; + case 'openPasswordGenerator': + // openPasswordGenerator has extended functionality if called in the vault + if (!this.router.url.includes('vault')) { + await this.openPasswordGenerator(); + } + break; } }); }); @@ -207,6 +274,59 @@ export class AppComponent implements OnInit { this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); } + async openExportVault() { + if (this.modal != null) { + this.modal.close(); + } + + const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent); + this.modal = this.exportVaultModalRef.createComponent(factory).instance; + const childComponent = this.modal.show(ExportComponent, this.exportVaultModalRef); + + childComponent.onSaved.subscribe(() => { + this.modal.close(); + }); + + this.modal.onClosed.subscribe(() => { + this.modal = null; + }); + } + + async addFolder() { + if (this.modal != null) { + this.modal.close(); + } + + const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent); + this.modal = this.folderAddEditModalRef.createComponent(factory).instance; + const childComponent = this.modal.show( + FolderAddEditComponent, this.folderAddEditModalRef, true, comp => comp.folderId = null); + + childComponent.onSavedFolder.subscribe(async () => { + this.modal.close(); + this.syncService.fullSync(false); + }); + + this.modal.onClosed.subscribe(() => { + this.modal = null; + }); + } + + async openPasswordGenerator() { + if (this.modal != null) { + this.modal.close(); + } + + const factory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent); + this.modal = this.passwordGeneratorModalRef.createComponent(factory).instance; + this.modal.show(PasswordGeneratorComponent, + this.passwordGeneratorModalRef, true, comp => comp.showSelect = false); + + this.modal.onClosed.subscribe(() => { + this.modal = null; + }); + } + private async updateAppMenu() { this.messagingService.send('updateAppMenu', { isAuthenticated: await this.userService.isAuthenticated(), @@ -320,4 +440,16 @@ export class AppComponent implements OnInit { } this.toasterService.popAsync(toast); } + + private routeToVault(action: string, cipherType: CipherType) { + if (!this.router.url.includes('vault')) { + this.router.navigate(['/vault'], { + queryParams: { + action: action, + addType: cipherType, + }, + replaceUrl: true, + }); + } + } } diff --git a/src/app/send/add-edit.component.html b/src/app/send/add-edit.component.html index 2ce51ad731..ae3260d638 100644 --- a/src/app/send/add-edit.component.html +++ b/src/app/send/add-edit.component.html @@ -1,56 +1,110 @@ - -
-
-
-
-
- {{'editSend' | i18n}} + +
+
+
+ + {{'sendDisabledWarning' | i18n}} + +
+
+
+ {{title}} +
+
+
+ +
-
-
- - +
+
+
+
+ +
+ +
+ + +
-
+
- + +
{{'sendFileDesc' | i18n}} {{'maxFileSize' | i18n}}
+ + +
+ + +
+
+
- + +
{{'sendTextDesc' | i18n}}
- +
-
+
+
+
+
+ {{'options' | i18n}} + + + +
+
+
-
- {{'options' | i18n}} -
-
+
- +
{{'deletionDateDesc' | i18n}}
-
+
+ + +
{{'deletionDateDesc' | i18n}}
+
+
- +
{{'expirationDateDesc' | i18n}}
+
+ + +
{{'expirationDateDesc' | i18n}}
+
- +
{{'maxAccessCountDesc' | i18n}}
-
+
@@ -58,10 +112,19 @@
-
- - -
{{'sendPasswordDesc' | i18n}}
+
+
+ + +
{{'sendPasswordDesc' | i18n}}
+
+
+ + + +
@@ -71,7 +134,7 @@
- + {{'sendNotesDesc' | i18n}}
@@ -80,27 +143,45 @@
- -
-
-
-
-
- {{'share' | i18n}} -
-
-
- - +
+
+
+ {{'share' | i18n}} +
+
+
+ + +
+
+ + +
+
+
- + + diff --git a/src/app/send/add-edit.component.ts b/src/app/send/add-edit.component.ts index bc9ccaa254..6b38b73d98 100644 --- a/src/app/send/add-edit.component.ts +++ b/src/app/send/add-edit.component.ts @@ -1,6 +1,6 @@ import { DatePipe } from '@angular/common'; -import { Component, Input } from '@angular/core'; +import { Component } from '@angular/core'; import { EnvironmentService } from 'jslib/abstractions/environment.service'; import { I18nService } from 'jslib/abstractions/i18n.service'; @@ -26,11 +26,21 @@ export class AddEditComponent extends BaseAddEditComponent { } async refresh() { + this.password = null; const send = await this.loadSend(); this.send = await send.decrypt(); this.hasPassword = this.send.password != null && this.send.password.trim() !== ''; this.deletionDate = this.dateToString(this.send.deletionDate); this.expirationDate = this.dateToString(this.send.expirationDate); - this.password = null; + } + + cancel() { + this.onCancelled.emit(this.send); + } + + copyLinkToClipboard(link: string) { + super.copyLinkToClipboard(link); + this.platformUtilsService.showToast('success', null, + this.i18nService.t('valueCopied', this.i18nService.t('sendLink'))); } } diff --git a/src/app/send/send.component.html b/src/app/send/send.component.html index ade3c8468a..05153e710b 100644 --- a/src/app/send/send.component.html +++ b/src/app/send/send.component.html @@ -42,8 +42,8 @@
- +