bitwarden-estensione-browser/apps/browser/src/popup/app.component.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

241 lines
10 KiB
TypeScript
Raw Normal View History

import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core";
2018-04-09 15:51:22 +02:00
import { NavigationEnd, Router, RouterOutlet } from "@angular/router";
import { filter, concatMap, Subject, takeUntil, firstValueFrom, tap, map } from "rxjs";
2021-12-21 15:43:35 +01:00
Auth/ps 2298 reorg auth (#4564) * Move auth service factories to Auth team * Move authentication componenets to Auth team * Move auth guard services to Auth team * Move Duo content script to Auth team * Move auth CLI commands to Auth team * Move Desktop Account components to Auth Team * Move Desktop guards to Auth team * Move two-factor provider images to Auth team * Move web Accounts components to Auth Team * Move web settings components to Auth Team * Move web two factor images to Auth Team * Fix missed import changes for Auth Team * Fix Linting errors * Fix missed CLI imports * Fix missed Desktop imports * Revert images move * Fix missed imports in Web * Move angular lib components to Auth Team * Move angular auth guards to Auth team * Move strategy specs to Auth team * Update .eslintignore for new paths * Move lib common abstractions to Auth team * Move services to Auth team * Move common lib enums to Auth team * Move webauthn iframe to Auth team * Move lib common domain models to Auth team * Move common lib requests to Auth team * Move response models to Auth team * Clean up whitelist * Move bit web components to Auth team * Move SSO and SCIM files to Auth team * Revert move SCIM to Auth team SCIM belongs to Admin Console team * Move captcha to Auth team * Move key connector to Auth team * Move emergency access to auth team * Delete extra file * linter fixes * Move kdf config to auth team * Fix whitelist * Fix duo autoformat * Complete two factor provider request move * Fix whitelist names * Fix login capitalization * Revert hint dependency reordering * Revert hint dependency reordering * Revert hint component This components is being picked up as a move between clients * Move web hint component to Auth team * Move new files to auth team * Fix desktop build * Fix browser build
2023-02-06 22:53:37 +01:00
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { MessageListener } from "@bitwarden/common/platform/messaging";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { DialogService, SimpleDialogOptions, ToastService } from "@bitwarden/components";
2021-12-21 15:43:35 +01:00
import { BrowserApi } from "../platform/browser/browser-api";
import { BrowserStateService } from "../platform/services/abstractions/browser-state.service";
import { BrowserSendStateService } from "../tools/popup/services/browser-send-state.service";
import { VaultBrowserStateService } from "../vault/services/vault-browser-state.service";
2021-12-21 15:43:35 +01:00
2018-04-08 06:30:04 +02:00
import { routerTransition } from "./app-routing.animations";
import { DesktopSyncVerificationDialogComponent } from "./components/desktop-sync-verification-dialog.component";
2018-04-08 06:30:04 +02:00
2018-04-04 04:14:54 +02:00
@Component({
selector: "app-root",
styles: [],
2018-04-08 06:30:04 +02:00
animations: [routerTransition],
template: ` <div [@routerTransition]="getState(o)">
2018-04-08 06:30:04 +02:00
<router-outlet #o="outlet"></router-outlet>
</div>`,
2018-04-04 04:14:54 +02:00
})
export class AppComponent implements OnInit, OnDestroy {
2018-04-06 21:33:20 +02:00
private lastActivity: number = null;
private activeUserId: string;
2021-12-21 15:43:35 +01:00
private destroy$ = new Subject<void>();
2021-12-07 20:42:18 +01:00
constructor(
2018-04-06 21:33:20 +02:00
private authService: AuthService,
2018-04-09 20:17:58 +02:00
private i18nService: I18nService,
private router: Router,
[PS-1854] Split services between background and visualizations (#4075) * Elevate Map <-> Record JSON helpers to Utils * Build Account from a StateService provided AccountDeserializer * Allow Manifest V2 usage of session sync Expands use of SessionSyncer to all Subject types. Correctly handles replay buffer for each type to ignore the flood of data upon subscription to each Subject type. * Create browser-synced Policy Service * Move BrowserFolderService * Libs account serialization improvements * Serialize Browser Accounts * Separate StateService in background/visualizations Visualizer state services share storages with background page, which nicely emulates mv3 synchronization through session/local storage. There should not be multithreading issues since all of these services are still running through a single thread, we just now have multiple places we are reading/writing data from. Smaller improvements * Rename browser's state service to BrowserStateService * Remove unused WithPrototype decorator :celebrate: * Removed conversion on withPrototypeForArrayMembers. It's reasonable to think that if the type is maintained, it doesn't need conversion. Eventually, we should be able to remove the withPrototypeForArrayMembers decorator as well, but that will require a bit more work on (de)serialization of the Accounts.data property. * Make Record <-> Map idempotent Should we get in a situation where we _think_ an object has been jsonified, but hasn't been, we need to correctly deal with the object received to create our target. * Check all requirements while duck typing * Name client services after the client * Use union type to limit initialize options * Fixup usages of `initializeAs` * Add OrganizationService to synced services Co-Authored-By: Daniel James Smith <djsmith85@users.noreply.github.com> * Add Settings service to synced services Co-Authored-By: Daniel James Smith <djsmith85@users.noreply.github.com> * Add missing BrowserStateService * Fix factories to use browser-specific service overides * Fix org-service registration in services.module * Revert "Add missing BrowserStateService" This reverts commit 81cf384e872718e86e84f9c54731e78a083e1ee3. * Fix session syncer tests * Fix synced item metadata tests * Early return null json objects * Prefer abstract service dependencies * Prefer minimal browser service overrides * [SG-632] - Change forwarded providers radio buttons list to dropdown (#4045) * SG-632 - Changed forwarded providers list of radio buttons to dropdown * SG-632 - Added role attributes to improve accessibility. * SG-632 - Added sorting to array and empty option * SG-632 - Fix styling to match standards. * rename cipehrs component to vault items component (#4081) * Update the version hash for the QA Web build artifact to follow SemVer syntax (#4102) * Remove extra call to toJSON() (#4101) Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com> Co-authored-by: Daniel James Smith <djsmith@web.de> Co-authored-by: Carlos Gonçalves <carlosmaccam@gmail.com> Co-authored-by: Jake Fink <jfink@bitwarden.com> Co-authored-by: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com> Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com>
2022-11-23 23:26:57 +01:00
private stateService: BrowserStateService,
private browserSendStateService: BrowserSendStateService,
private vaultBrowserStateService: VaultBrowserStateService,
private cipherService: CipherService,
2018-10-03 05:33:56 +02:00
private changeDetectorRef: ChangeDetectorRef,
private ngZone: NgZone,
private platformUtilsService: PlatformUtilsService,
private dialogService: DialogService,
private messageListener: MessageListener,
private toastService: ToastService,
2021-12-21 15:43:35 +01:00
) {}
async ngOnInit() {
// Component states must not persist between closing and reopening the popup, otherwise they become dead objects
// Clear them aggressively to make sure this doesn't occur
await this.clearComponentStates();
this.stateService.activeAccount$.pipe(takeUntil(this.destroy$)).subscribe((userId) => {
this.activeUserId = userId;
[Account Switching] Base changes for account switching (#2250) * Pull in jslib * Create new state models * Create browser specific stateService * Remove registration deprecated services, register stateService * Replace usage of deprecated services (user, constants) * Add missing properties to BrowserGroupingsComponentState * Remove StorageService from initFactory * Clear the correct state * Add null check when restoring send-grouping state * add remember email * Initialize stateservice in services.module * Fix 'lock now' not working * Comment to remove setting defaults on install * Pull jslib * Remove setting defaults on install * Bump jslib * Pass the current userId to services when logging out * Bump jslib * Override vaultTimeout default on account addition * Pull latest jslib * Retrieve vaultTimeout from stateService * Record activity per Account * Add userId to logout and add fallback if not present * Register AccountFactory * Pass userId in messages * Base changes for account switching di fixes (#2280) * [bug] Null checks on Account init * [bug] Use same stateService instance for all operations We override the stateService in browser, but currently don't pull the background service into popup and allow jslib to create its own instance of the base StateService for jslib services. This causes a split in in memory state between the three isntances that results in many errors, namely locking not working. * [chore] Update jslib * Pull in jslib * Pull in jslib * Pull in latest jslib to multiple stateservice inits * Check vault states before executing processReload * Adjust iterator * Update native messaging to include the userId (#2290) * Re-Add UserVerificationService * Fix email not being remembered by base component * Improve readability of reloadProcess * Removed unneeded null check * Fix constructor dependency (stateService) * Added missing await * Simplify dependency registration * Fixed typos * Reverted back to simple loop * Use vaultTimeoutService to retrieve Timeout Co-authored-by: Addison Beck <abeck@bitwarden.com> Co-authored-by: Oscar Hinton <oscar@oscarhinton.com>
2022-01-27 22:22:51 +01:00
});
2018-04-04 04:14:54 +02:00
this.authService.activeAccountStatus$
.pipe(
map((status) => status === AuthenticationStatus.Unlocked),
filter((unlocked) => unlocked),
concatMap(async () => {
await this.recordActivity();
}),
takeUntil(this.destroy$),
)
.subscribe();
2018-04-06 21:33:20 +02:00
this.ngZone.runOutsideAngular(() => {
window.onmousedown = () => this.recordActivity();
window.ontouchstart = () => this.recordActivity();
window.onclick = () => this.recordActivity();
window.onscroll = () => this.recordActivity();
window.onkeypress = () => this.recordActivity();
2021-12-21 15:43:35 +01:00
});
this.messageListener.allMessages$
.pipe(
tap((msg: any) => {
if (msg.command === "doneLoggingOut") {
this.authService.logOut(async () => {
if (msg.expired) {
this.toastService.showToast({
variant: "warning",
title: this.i18nService.t("loggedOut"),
message: this.i18nService.t("loginExpired"),
});
}
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["home"]);
});
this.changeDetectorRef.detectChanges();
} else if (msg.command === "authBlocked") {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["home"]);
} else if (
msg.command === "locked" &&
(msg.userId == null || msg.userId == this.activeUserId)
) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["lock"]);
} else if (msg.command === "showDialog") {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.showDialog(msg);
} else if (msg.command === "showNativeMessagingFinterprintDialog") {
// TODO: Should be refactored to live in another service.
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.showNativeMessagingFingerprintDialog(msg);
} else if (msg.command === "showToast") {
this.toastService._showToast(msg);
} else if (msg.command === "reloadProcess") {
const forceWindowReload =
this.platformUtilsService.isSafari() ||
this.platformUtilsService.isFirefox() ||
this.platformUtilsService.isOpera();
// Wait to make sure background has reloaded first.
window.setTimeout(
() => BrowserApi.reloadExtension(forceWindowReload ? window : null),
2000,
);
} else if (msg.command === "reloadPopup") {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["/"]);
} else if (msg.command === "convertAccountToKeyConnector") {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["/remove-password"]);
} else if (msg.command === "switchAccountFinish") {
// TODO: unset loading?
// this.loading = false;
} else if (msg.command == "update-temp-password") {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["/update-temp-password"]);
}
}),
takeUntil(this.destroy$),
)
.subscribe();
2021-12-21 15:43:35 +01:00
// eslint-disable-next-line rxjs/no-async-subscribe
this.router.events.pipe(takeUntil(this.destroy$)).subscribe(async (event) => {
if (event instanceof NavigationEnd) {
const url = event.urlAfterRedirects || event.url || "";
2021-12-21 15:43:35 +01:00
if (
url.startsWith("/tabs/") &&
(window as any).previousPopupUrl != null &&
(window as any).previousPopupUrl.startsWith("/tabs/")
2021-12-21 15:43:35 +01:00
) {
await this.clearComponentStates();
}
2018-04-06 21:33:20 +02:00
if (url.startsWith("/tabs/")) {
await this.cipherService.setAddEditCipherInfo(null);
2018-04-06 21:33:20 +02:00
}
(window as any).previousPopupUrl = url;
2018-04-06 21:33:20 +02:00
// Clear route direction after animation (400ms)
2018-10-03 15:51:14 +02:00
if ((window as any).routeDirection != null) {
2021-12-07 20:42:18 +01:00
window.setTimeout(() => {
(window as any).routeDirection = null;
2018-10-03 05:33:56 +02:00
}, 400);
}
2021-12-21 15:43:35 +01:00
}
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
2018-10-03 05:33:56 +02:00
getState(outlet: RouterOutlet) {
if (outlet.activatedRouteData.state === "ciphers") {
2021-12-07 20:42:18 +01:00
const routeDirection =
2018-10-03 05:33:56 +02:00
(window as any).routeDirection != null ? (window as any).routeDirection : "";
2021-12-21 15:43:35 +01:00
return (
2021-12-07 20:42:18 +01:00
"ciphers_direction=" +
routeDirection +
2021-12-21 15:43:35 +01:00
"_" +
2021-12-07 20:42:18 +01:00
(outlet.activatedRoute.queryParams as any).value.folderId +
2021-12-21 15:43:35 +01:00
"_" +
2021-12-07 20:42:18 +01:00
(outlet.activatedRoute.queryParams as any).value.collectionId
);
} else {
return outlet.activatedRouteData.state;
2018-10-03 05:33:56 +02:00
}
2021-12-21 15:43:35 +01:00
}
2018-10-03 05:33:56 +02:00
private async recordActivity() {
if (this.activeUserId == null) {
return;
}
const now = new Date().getTime();
if (this.lastActivity != null && now - this.lastActivity < 250) {
2018-04-13 05:18:51 +02:00
return;
}
this.lastActivity = now;
await this.stateService.setLastActive(now, { userId: this.activeUserId });
2021-12-21 15:43:35 +01:00
}
private showToast(msg: any) {
this.platformUtilsService.showToast(msg.type, msg.title, msg.text, msg.options);
2021-12-21 15:43:35 +01:00
}
private async showDialog(msg: SimpleDialogOptions) {
await this.dialogService.openSimpleDialog(msg);
}
2021-12-21 15:43:35 +01:00
private async showNativeMessagingFingerprintDialog(msg: any) {
const dialogRef = DesktopSyncVerificationDialogComponent.open(this.dialogService, {
fingerprint: msg.fingerprint,
2021-12-21 15:43:35 +01:00
});
return firstValueFrom(dialogRef.closed);
2021-12-21 15:43:35 +01:00
}
private async clearComponentStates() {
if (!(await this.stateService.getIsAuthenticated())) {
return;
}
await Promise.all([
this.vaultBrowserStateService.setBrowserGroupingsComponentState(null),
this.vaultBrowserStateService.setBrowserVaultItemsComponentState(null),
this.browserSendStateService.setBrowserSendComponentState(null),
this.browserSendStateService.setBrowserSendTypeComponentState(null),
]);
}
2018-04-04 04:14:54 +02:00
}