diff --git a/src/popup2/accounts/two-factor.component.ts b/src/popup2/accounts/two-factor.component.ts index 73869987487..c0da93e0e39 100644 --- a/src/popup2/accounts/two-factor.component.ts +++ b/src/popup2/accounts/two-factor.component.ts @@ -1,4 +1,10 @@ -import { Component } from '@angular/core'; +import { + ChangeDetectorRef, + Component, + NgZone, + OnDestroy, + OnInit, +} from '@angular/core'; import { Router } from '@angular/router'; @@ -16,8 +22,12 @@ import { I18nService } from 'jslib/abstractions/i18n.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { SyncService } from 'jslib/abstractions/sync.service'; +import { BroadcasterService } from 'jslib/angular/services/broadcaster.service'; + import { TwoFactorComponent as BaseTwoFactorComponent } from 'jslib/angular/components/two-factor.component'; +const BroadcasterSubscriptionId = 'TwoFactorComponent'; + @Component({ selector: 'app-two-factor', templateUrl: 'two-factor.component.html', @@ -29,13 +39,30 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { analytics: Angulartics2, toasterService: ToasterService, i18nService: I18nService, apiService: ApiService, platformUtilsService: PlatformUtilsService, syncService: SyncService, - environmentService: EnvironmentService) { + environmentService: EnvironmentService, private ngZone: NgZone, + private broadcasterService: BroadcasterService, private changeDetectorRef: ChangeDetectorRef) { super(authService, router, analytics, toasterService, i18nService, apiService, platformUtilsService, syncService, window, environmentService); this.successRoute = '/tabs/vault'; } async ngOnInit() { + this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { + this.ngZone.run(async () => { + switch (message.command) { + case '2faPageResponse': + if (message.type === 'duo') { + this.token = message.data.sigValue; + this.submitWithTab(message.webExtSender.tab); + } + default: + break; + } + + this.changeDetectorRef.detectChanges(); + }) + }); + this.showNewWindowMessage = this.platformUtilsService.isSafari(); await super.ngOnInit(); @@ -62,11 +89,26 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { } }); }, 500); + } - // TODO: listen for duo data message response + ngOnDestroy() { + this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); + super.ngOnDestroy(); } anotherMethod() { this.router.navigate(['2fa-options']); } + + async submitWithTab(sendSuccessToTab: any) { + await super.submit(); + if (sendSuccessToTab != null) { + window.setTimeout(() => { + BrowserApi.tabSendMessage(sendSuccessToTab, { + command: '2faPageData', + data: { type: 'success' } + }); + }, 1000); + } + } } diff --git a/src/popup2/app.component.ts b/src/popup2/app.component.ts index 651588ea6a8..46e15160ebd 100644 --- a/src/popup2/app.component.ts +++ b/src/popup2/app.component.ts @@ -6,19 +6,23 @@ import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; import { Component, - ComponentFactoryResolver, - NgZone, - OnDestroy, OnInit, - Type, - ViewChild, - ViewContainerRef, } from '@angular/core'; import { Router } from '@angular/router'; import { ToasterService } from 'angular2-toaster'; import { Angulartics2 } from 'angulartics2'; +import { BrowserApi } from '../browser/browserApi'; + +import { BroadcasterService } from 'jslib/angular/services/broadcaster.service'; + +import { AuthService } from 'jslib/abstractions/auth.service'; +import { I18nService } from 'jslib/abstractions/i18n.service'; +import { StorageService } from 'jslib/abstractions/storage.service'; + +import { ConstantsService } from 'jslib/services/constants.service'; + @Component({ selector: 'app-root', styles: [], @@ -26,7 +30,7 @@ import { Angulartics2 } from 'angulartics2'; `, }) -export class AppComponent { +export class AppComponent implements OnInit { toasterConfig: ToasterConfig = new ToasterConfig({ showCloseButton: true, mouseoverTimerStop: true, @@ -36,6 +40,47 @@ export class AppComponent { newestOnTop: false }); + private lastActivity: number = null; + constructor(private angulartics2GoogleAnalytics: Angulartics2GoogleAnalytics, private analytics: Angulartics2, - private toasterService: ToasterService) { } + private toasterService: ToasterService, private storageService: StorageService, + private broadcasterService: BroadcasterService, private authService: AuthService, + private i18nService: I18nService, private router: Router) { } + + ngOnInit() { + window.onmousemove = () => this.recordActivity(); + window.onmousedown = () => this.recordActivity(); + window.ontouchstart = () => this.recordActivity(); + window.onclick = () => this.recordActivity(); + window.onscroll = () => this.recordActivity(); + window.onkeypress = () => this.recordActivity(); + + (window as any).bitwardenPopupMainMessageListener = (msg: any, sender: any, sendResponse: any) => { + if (msg.command === 'doneLoggingOut') { + this.authService.logOut(() => { + this.analytics.eventTrack.next({ action: 'Logged Out' }); + if (msg.expired) { + this.toasterService.popAsync('warning', this.i18nService.t('loggedOut'), + this.i18nService.t('loginExpired')); + } + this.router.navigate(['home']); + }); + } else { + msg.webExtSender = sender; + this.broadcasterService.send(msg); + } + }; + + BrowserApi.messageListener((window as any).bitwardenPopupMainMessageListener); + } + + private async recordActivity() { + const now = (new Date()).getTime(); + if (this.lastActivity != null && now - this.lastActivity < 250) { + return; + } + + this.lastActivity = now; + this.storageService.save(ConstantsService.lastActiveKey, now); + } } diff --git a/src/popup2/services/services.module.ts b/src/popup2/services/services.module.ts index 7ac1bc6c4bb..66dff1297a6 100644 --- a/src/popup2/services/services.module.ts +++ b/src/popup2/services/services.module.ts @@ -6,6 +6,7 @@ import { import { ToasterModule } from 'angular2-toaster'; import { AuthGuardService } from 'jslib/angular/services/auth-guard.service'; +import { BroadcasterService } from 'jslib/angular/services/broadcaster.service'; import { ValidationService } from 'jslib/angular/services/validation.service'; import { BrowserApi } from '../../browser/browserApi'; @@ -79,6 +80,7 @@ export function initFactory(i18nService: I18nService, storageService: StorageSer ValidationService, AuthGuardService, PopupUtilsService, + BroadcasterService, { provide: MessagingService, useValue: messagingService }, { provide: AuthServiceAbstraction, useValue: authService }, { provide: StateServiceAbstraction, useValue: stateService }, diff --git a/src/popup2/vault/ciphers.component.ts b/src/popup2/vault/ciphers.component.ts index a72ac0a9203..cfb8eadc4f9 100644 --- a/src/popup2/vault/ciphers.component.ts +++ b/src/popup2/vault/ciphers.component.ts @@ -1,6 +1,9 @@ import { Location } from '@angular/common'; import { + ChangeDetectorRef, Component, + NgZone, + OnDestroy, OnInit, } from '@angular/core'; import { @@ -12,15 +15,21 @@ import { CipherService } from 'jslib/abstractions/cipher.service'; import { CipherView } from 'jslib/models/view/cipherView'; +import { BroadcasterService } from 'jslib/angular/services/broadcaster.service'; + import { CiphersComponent as BaseCiphersComponent } from 'jslib/angular/components/ciphers.component'; +const BroadcasterSubscriptionId = 'CiphersComponent'; + @Component({ selector: 'app-vault-ciphers', templateUrl: 'ciphers.component.html', }) -export class CiphersComponent extends BaseCiphersComponent implements OnInit { +export class CiphersComponent extends BaseCiphersComponent implements OnInit, OnDestroy { constructor(cipherService: CipherService, private route: ActivatedRoute, - private router: Router, private location: Location) { + private router: Router, private location: Location, + private ngZone: NgZone, private broadcasterService: BroadcasterService, + private changeDetectorRef: ChangeDetectorRef) { super(cipherService); } @@ -37,6 +46,26 @@ export class CiphersComponent extends BaseCiphersComponent implements OnInit { await super.load(); } }); + + this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { + this.ngZone.run(async () => { + switch (message.command) { + case 'syncCompleted': + window.setTimeout(() => { + this.load(); + }, 500); + break; + default: + break; + } + + this.changeDetectorRef.detectChanges(); + }) + }); + } + + ngOnDestroy() { + this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); } selectCipher(cipher: CipherView) { diff --git a/src/popup2/vault/current-tab.component.ts b/src/popup2/vault/current-tab.component.ts index a60a53767f8..e5676541ec7 100644 --- a/src/popup2/vault/current-tab.component.ts +++ b/src/popup2/vault/current-tab.component.ts @@ -1,12 +1,9 @@ import { + ChangeDetectorRef, Component, - ComponentFactoryResolver, NgZone, OnDestroy, OnInit, - Type, - ViewChild, - ViewContainerRef, } from '@angular/core'; import { Router } from '@angular/router'; @@ -15,6 +12,8 @@ import { Angulartics2 } from 'angulartics2'; import { BrowserApi } from '../../browser/browserApi'; +import { BroadcasterService } from 'jslib/angular/services/broadcaster.service'; + import { CipherType } from 'jslib/enums/cipherType'; import { CipherView } from 'jslib/models/view/cipherView'; @@ -26,12 +25,15 @@ import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { AutofillService } from '../../services/abstractions/autofill.service'; import { PopupUtilsService } from '../services/popup-utils.service'; +import { setTimeout } from 'timers'; + +const BroadcasterSubscriptionId = 'CurrentTabComponent'; @Component({ selector: 'app-current-tab', templateUrl: 'current-tab.component.html', }) -export class CurrentTabComponent implements OnInit { +export class CurrentTabComponent implements OnInit, OnDestroy { pageDetails: any[] = []; cardCiphers: CipherView[]; identityCiphers: CipherView[]; @@ -48,16 +50,49 @@ export class CurrentTabComponent implements OnInit { constructor(private platformUtilsService: PlatformUtilsService, private cipherService: CipherService, private popupUtilsService: PopupUtilsService, private autofillService: AutofillService, private analytics: Angulartics2, private toasterService: ToasterService, - private i18nService: I18nService, private router: Router) { + private i18nService: I18nService, private router: Router, + private ngZone: NgZone, private broadcasterService: BroadcasterService, + private changeDetectorRef: ChangeDetectorRef) { this.inSidebar = popupUtilsService.inSidebar(window); this.showPopout = !this.inSidebar && !platformUtilsService.isSafari(); this.disableSearch = platformUtilsService.isEdge(); } ngOnInit() { + this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { + this.ngZone.run(async () => { + switch (message.command) { + case 'syncCompleted': + if (this.loaded) { + setTimeout(() => { + this.load(); + }, 500); + } + break; + case 'collectPageDetailsResponse': + if (message.sender === BroadcasterSubscriptionId) { + this.pageDetails.push({ + frameId: message.webExtSender.frameId, + tab: message.tab, + details: message.details, + }); + } + break; + default: + break; + } + + this.changeDetectorRef.detectChanges(); + }) + }); + this.load(); } + ngOnDestroy() { + this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); + } + async refresh() { await this.load(); } @@ -117,7 +152,7 @@ export class CurrentTabComponent implements OnInit { BrowserApi.tabSendMessage(tab, { command: 'collectPageDetails', tab: tab, - sender: 'currentController', + sender: BroadcasterSubscriptionId, }).then(() => { this.canAutofill = true; }); diff --git a/src/popup2/vault/groupings.component.ts b/src/popup2/vault/groupings.component.ts index c974434cab4..3d6341df445 100644 --- a/src/popup2/vault/groupings.component.ts +++ b/src/popup2/vault/groupings.component.ts @@ -1,5 +1,8 @@ import { + ChangeDetectorRef, Component, + NgZone, + OnDestroy, OnInit, } from '@angular/core'; import { Router } from '@angular/router'; @@ -14,13 +17,17 @@ import { CollectionService } from 'jslib/abstractions/collection.service'; import { CipherService } from 'jslib/abstractions/cipher.service'; import { FolderService } from 'jslib/abstractions/folder.service'; +import { BroadcasterService } from 'jslib/angular/services/broadcaster.service'; + import { GroupingsComponent as BaseGroupingsComponent } from 'jslib/angular/components/groupings.component'; +const BroadcasterSubscriptionId = 'GroupingsComponent'; + @Component({ selector: 'app-vault-groupings', templateUrl: 'groupings.component.html', }) -export class GroupingsComponent extends BaseGroupingsComponent implements OnInit { +export class GroupingsComponent extends BaseGroupingsComponent implements OnInit, OnDestroy { ciphers: CipherView[]; favoriteCiphers: CipherView[]; noFolderCiphers: CipherView[]; @@ -31,11 +38,37 @@ export class GroupingsComponent extends BaseGroupingsComponent implements OnInit showNoFolderCiphers = false; constructor(collectionService: CollectionService, folderService: FolderService, - private cipherService: CipherService, private router: Router) { + private cipherService: CipherService, private router: Router, + private ngZone: NgZone, private broadcasterService: BroadcasterService, + private changeDetectorRef: ChangeDetectorRef) { super(collectionService, folderService); } - async ngOnInit() { + ngOnInit() { + this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { + this.ngZone.run(async () => { + switch (message.command) { + case 'syncCompleted': + window.setTimeout(() => { + this.load(); + }, 500); + break; + default: + break; + } + + this.changeDetectorRef.detectChanges(); + }) + }); + + this.load(); + } + + ngOnDestroy() { + this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); + } + + async load() { await super.load(); super.loaded = false;