2022-06-23 15:38:12 +02:00
|
|
|
import {
|
|
|
|
ChangeDetectorRef,
|
|
|
|
Component,
|
|
|
|
NgZone,
|
|
|
|
OnDestroy,
|
|
|
|
OnInit,
|
|
|
|
SecurityContext,
|
|
|
|
} from "@angular/core";
|
2018-10-03 05:33:56 +02:00
|
|
|
import { DomSanitizer } from "@angular/platform-browser";
|
2018-04-09 15:51:22 +02:00
|
|
|
import { NavigationEnd, Router, RouterOutlet } from "@angular/router";
|
2021-12-07 20:42:18 +01:00
|
|
|
import { IndividualConfig, ToastrService } from "ngx-toastr";
|
2023-05-03 21:28:14 +02:00
|
|
|
import { filter, concatMap, Subject, takeUntil } from "rxjs";
|
2023-05-02 18:46:03 +02:00
|
|
|
import Swal from "sweetalert2";
|
2021-12-21 15:43:35 +01:00
|
|
|
|
2023-02-06 22:53:37 +01:00
|
|
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
2023-06-06 22:34:53 +02:00
|
|
|
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
|
|
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
|
|
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
|
|
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
2023-08-16 14:26:56 +02:00
|
|
|
import { DialogService, SimpleDialogOptions } from "@bitwarden/components";
|
2021-12-21 15:43:35 +01:00
|
|
|
|
2023-06-06 22:34:53 +02:00
|
|
|
import { BrowserApi } from "../platform/browser/browser-api";
|
|
|
|
import { BrowserStateService } from "../platform/services/abstractions/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";
|
|
|
|
|
2018-04-04 04:14:54 +02:00
|
|
|
@Component({
|
|
|
|
selector: "app-root",
|
|
|
|
styles: [],
|
2018-04-08 06:30:04 +02:00
|
|
|
animations: [routerTransition],
|
2022-04-29 02:06:33 +02:00
|
|
|
template: ` <div [@routerTransition]="getState(o)">
|
2018-04-08 06:30:04 +02:00
|
|
|
<router-outlet #o="outlet"></router-outlet>
|
2022-04-29 02:06:33 +02:00
|
|
|
</div>`,
|
2018-04-04 04:14:54 +02:00
|
|
|
})
|
2022-06-23 15:38:12 +02:00
|
|
|
export class AppComponent implements OnInit, OnDestroy {
|
2018-04-06 21:33:20 +02:00
|
|
|
private lastActivity: number = null;
|
2022-02-07 17:41:51 +01:00
|
|
|
private activeUserId: string;
|
2021-12-21 15:43:35 +01:00
|
|
|
|
2022-08-26 18:09:28 +02:00
|
|
|
private destroy$ = new Subject<void>();
|
2022-06-23 15:38:12 +02:00
|
|
|
|
2021-12-07 20:42:18 +01:00
|
|
|
constructor(
|
|
|
|
private toastrService: ToastrService,
|
2018-04-06 21:33:20 +02:00
|
|
|
private broadcasterService: BroadcasterService,
|
|
|
|
private authService: AuthService,
|
2018-04-09 20:17:58 +02:00
|
|
|
private i18nService: I18nService,
|
|
|
|
private router: Router,
|
2022-11-23 23:26:57 +01:00
|
|
|
private stateService: BrowserStateService,
|
2018-04-10 23:14:10 +02:00
|
|
|
private messagingService: MessagingService,
|
2018-10-03 05:33:56 +02:00
|
|
|
private changeDetectorRef: ChangeDetectorRef,
|
|
|
|
private ngZone: NgZone,
|
2021-11-09 18:59:51 +01:00
|
|
|
private sanitizer: DomSanitizer,
|
2023-05-02 18:46:03 +02:00
|
|
|
private platformUtilsService: PlatformUtilsService,
|
2023-08-16 14:26:56 +02:00
|
|
|
private dialogService: DialogService
|
2021-12-21 15:43:35 +01:00
|
|
|
) {}
|
|
|
|
|
2022-03-18 00:01:22 +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();
|
|
|
|
|
2022-08-09 21:11:51 +02:00
|
|
|
this.stateService.activeAccount$.pipe(takeUntil(this.destroy$)).subscribe((userId) => {
|
2022-02-07 17:41:51 +01:00
|
|
|
this.activeUserId = userId;
|
2022-01-27 22:22:51 +01:00
|
|
|
});
|
2018-04-04 04:14:54 +02:00
|
|
|
|
2023-05-03 21:28:14 +02:00
|
|
|
this.stateService.activeAccountUnlocked$
|
|
|
|
.pipe(
|
|
|
|
filter((unlocked) => unlocked),
|
|
|
|
concatMap(async () => {
|
|
|
|
await this.recordActivity();
|
|
|
|
}),
|
|
|
|
takeUntil(this.destroy$)
|
|
|
|
)
|
|
|
|
.subscribe();
|
|
|
|
|
2018-04-06 21:33:20 +02:00
|
|
|
this.ngZone.runOutsideAngular(() => {
|
2022-02-07 17:41:51 +01:00
|
|
|
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
|
|
|
});
|
|
|
|
|
2021-12-07 20:42:18 +01:00
|
|
|
(window as any).bitwardenPopupMainMessageListener = async (
|
|
|
|
msg: any,
|
|
|
|
sender: any,
|
2018-04-06 21:33:20 +02:00
|
|
|
sendResponse: any
|
2021-12-21 15:43:35 +01:00
|
|
|
) => {
|
2018-04-06 21:33:20 +02:00
|
|
|
if (msg.command === "doneLoggingOut") {
|
|
|
|
this.ngZone.run(async () => {
|
2022-01-27 22:22:51 +01:00
|
|
|
this.authService.logOut(async () => {
|
2018-04-10 23:14:10 +02:00
|
|
|
if (msg.expired) {
|
2018-04-06 21:33:20 +02:00
|
|
|
this.showToast({
|
|
|
|
type: "warning",
|
2018-04-09 20:17:58 +02:00
|
|
|
title: this.i18nService.t("loggedOut"),
|
|
|
|
text: this.i18nService.t("loginExpired"),
|
2021-12-21 15:43:35 +01:00
|
|
|
});
|
|
|
|
}
|
2022-01-27 22:22:51 +01:00
|
|
|
|
2022-08-09 21:11:51 +02:00
|
|
|
if (this.activeUserId === null) {
|
2022-01-27 22:22:51 +01:00
|
|
|
this.router.navigate(["home"]);
|
|
|
|
}
|
2021-12-21 15:43:35 +01:00
|
|
|
});
|
2018-10-03 05:33:56 +02:00
|
|
|
this.changeDetectorRef.detectChanges();
|
2021-12-21 15:43:35 +01:00
|
|
|
});
|
2021-11-09 18:59:51 +01:00
|
|
|
} else if (msg.command === "authBlocked") {
|
2019-04-18 16:11:01 +02:00
|
|
|
this.ngZone.run(() => {
|
2021-11-09 18:59:51 +01:00
|
|
|
this.router.navigate(["home"]);
|
2021-12-21 15:43:35 +01:00
|
|
|
});
|
2021-11-09 18:59:51 +01:00
|
|
|
} else if (msg.command === "locked") {
|
2022-01-27 22:22:51 +01:00
|
|
|
if (msg.userId == null || msg.userId === (await this.stateService.getUserId())) {
|
|
|
|
this.ngZone.run(() => {
|
|
|
|
this.router.navigate(["lock"]);
|
|
|
|
});
|
|
|
|
}
|
2018-04-10 20:20:03 +02:00
|
|
|
} else if (msg.command === "showDialog") {
|
2021-11-09 18:59:51 +01:00
|
|
|
await this.showDialog(msg);
|
2023-05-02 18:46:03 +02:00
|
|
|
} else if (msg.command === "showNativeMessagingFinterprintDialog") {
|
|
|
|
// TODO: Should be refactored to live in another service.
|
|
|
|
await this.showNativeMessagingFingerprintDialog(msg);
|
2018-10-03 05:33:56 +02:00
|
|
|
} else if (msg.command === "showToast") {
|
2018-10-03 15:51:14 +02:00
|
|
|
this.ngZone.run(() => {
|
|
|
|
this.showToast(msg);
|
2021-12-21 15:43:35 +01:00
|
|
|
});
|
2021-11-09 18:59:51 +01:00
|
|
|
} else if (msg.command === "reloadProcess") {
|
2022-08-05 20:04:27 +02:00
|
|
|
const forceWindowReload =
|
2021-11-09 18:59:51 +01:00
|
|
|
this.platformUtilsService.isSafari() ||
|
|
|
|
this.platformUtilsService.isFirefox() ||
|
|
|
|
this.platformUtilsService.isOpera();
|
2022-08-05 20:04:27 +02:00
|
|
|
// Wait to make sure background has reloaded first.
|
|
|
|
window.setTimeout(
|
|
|
|
() => BrowserApi.reloadExtension(forceWindowReload ? window : null),
|
|
|
|
2000
|
|
|
|
);
|
2018-04-12 20:24:42 +02:00
|
|
|
} else if (msg.command === "reloadPopup") {
|
|
|
|
this.ngZone.run(() => {
|
|
|
|
this.router.navigate(["/"]);
|
|
|
|
});
|
2019-08-17 02:48:01 +02:00
|
|
|
} else if (msg.command === "convertAccountToKeyConnector") {
|
2018-10-03 15:51:14 +02:00
|
|
|
this.ngZone.run(async () => {
|
2021-02-12 23:28:31 +01:00
|
|
|
this.router.navigate(["/remove-password"]);
|
2018-04-09 20:17:58 +02:00
|
|
|
});
|
2020-09-15 16:50:45 +02:00
|
|
|
} else {
|
|
|
|
msg.webExtSender = sender;
|
|
|
|
this.broadcasterService.send(msg);
|
2021-12-21 15:43:35 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-10-29 13:58:20 +01:00
|
|
|
BrowserApi.messageListener("app.component", (window as any).bitwardenPopupMainMessageListener);
|
2021-12-21 15:43:35 +01:00
|
|
|
|
2022-08-26 18:09:28 +02:00
|
|
|
// eslint-disable-next-line rxjs/no-async-subscribe
|
2022-06-23 15:38:12 +02:00
|
|
|
this.router.events.pipe(takeUntil(this.destroy$)).subscribe(async (event) => {
|
2018-10-29 13:58:20 +01:00
|
|
|
if (event instanceof NavigationEnd) {
|
|
|
|
const url = event.urlAfterRedirects || event.url || "";
|
2021-12-21 15:43:35 +01:00
|
|
|
if (
|
2018-10-29 14:29:21 +01:00
|
|
|
url.startsWith("/tabs/") &&
|
2018-10-29 13:58:20 +01:00
|
|
|
(window as any).previousPopupUrl != null &&
|
2018-10-26 18:37:55 +02:00
|
|
|
(window as any).previousPopupUrl.startsWith("/tabs/")
|
2021-12-21 15:43:35 +01:00
|
|
|
) {
|
2022-03-18 00:01:22 +01:00
|
|
|
await this.clearComponentStates();
|
2018-10-26 18:37:55 +02:00
|
|
|
}
|
2018-04-06 21:33:20 +02:00
|
|
|
if (url.startsWith("/tabs/")) {
|
2022-01-27 22:22:51 +01:00
|
|
|
await this.stateService.setAddEditCipherInfo(null);
|
2018-04-06 21:33:20 +02:00
|
|
|
}
|
2018-10-29 14:29:21 +01: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
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-06-23 15:38:12 +02: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
|
|
|
|
2022-02-07 17:41:51 +01:00
|
|
|
private async recordActivity() {
|
|
|
|
if (this.activeUserId == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-03-04 17:42:21 +01:00
|
|
|
const now = new Date().getTime();
|
|
|
|
if (this.lastActivity != null && now - this.lastActivity < 250) {
|
2018-04-13 05:18:51 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-03-04 17:42:21 +01:00
|
|
|
this.lastActivity = now;
|
2022-02-07 17:41:51 +01:00
|
|
|
await this.stateService.setLastActive(now, { userId: this.activeUserId });
|
2021-12-21 15:43:35 +01:00
|
|
|
}
|
|
|
|
|
2020-03-04 17:42:21 +01:00
|
|
|
private showToast(msg: any) {
|
|
|
|
let message = "";
|
2021-12-21 15:43:35 +01:00
|
|
|
|
2020-03-04 17:42:21 +01:00
|
|
|
const options: Partial<IndividualConfig> = {};
|
2021-12-21 15:43:35 +01:00
|
|
|
|
2020-03-04 17:42:21 +01:00
|
|
|
if (typeof msg.text === "string") {
|
|
|
|
message = msg.text;
|
2021-05-13 21:22:49 +02:00
|
|
|
} else if (msg.text.length === 1) {
|
2020-03-04 17:42:21 +01:00
|
|
|
message = msg.text[0];
|
2021-12-21 15:43:35 +01:00
|
|
|
} else {
|
2020-03-04 17:42:21 +01:00
|
|
|
msg.text.forEach(
|
|
|
|
(t: string) =>
|
|
|
|
(message += "<p>" + this.sanitizer.sanitize(SecurityContext.HTML, t) + "</p>")
|
2021-12-21 15:43:35 +01:00
|
|
|
);
|
2021-12-07 20:42:18 +01:00
|
|
|
options.enableHtml = true;
|
2021-12-21 15:43:35 +01:00
|
|
|
}
|
2018-10-03 05:33:56 +02:00
|
|
|
if (msg.options != null) {
|
2020-03-04 17:42:21 +01:00
|
|
|
if (msg.options.trustedHtml === true) {
|
2021-12-07 20:42:18 +01:00
|
|
|
options.enableHtml = true;
|
2021-12-21 15:43:35 +01:00
|
|
|
}
|
2020-03-04 17:42:21 +01:00
|
|
|
if (msg.options.timeout != null && msg.options.timeout > 0) {
|
|
|
|
options.timeOut = msg.options.timeout;
|
2018-04-13 05:18:51 +02:00
|
|
|
}
|
2021-12-21 15:43:35 +01:00
|
|
|
}
|
2018-04-13 05:18:51 +02:00
|
|
|
|
|
|
|
this.toastrService.show(message, msg.title, options, "toast-" + msg.type);
|
2021-12-21 15:43:35 +01:00
|
|
|
}
|
|
|
|
|
2023-05-02 18:46:03 +02:00
|
|
|
private async showDialog(msg: SimpleDialogOptions) {
|
|
|
|
await this.dialogService.openSimpleDialog(msg);
|
|
|
|
}
|
2021-12-21 15:43:35 +01:00
|
|
|
|
2023-05-02 18:46:03 +02:00
|
|
|
private async showNativeMessagingFingerprintDialog(msg: any) {
|
|
|
|
await Swal.fire({
|
2020-03-04 17:42:21 +01:00
|
|
|
heightAuto: false,
|
|
|
|
buttonsStyling: false,
|
2023-05-02 18:46:03 +02:00
|
|
|
icon: "warning",
|
|
|
|
iconHtml: '<i class="swal-custom-icon bwi bwi-exclamation-triangle text-warning"></i>',
|
|
|
|
html: `${this.i18nService.t("desktopIntegrationVerificationText")}<br><br><strong>${
|
|
|
|
msg.fingerprint
|
|
|
|
}</strong>`,
|
|
|
|
titleText: this.i18nService.t("desktopSyncVerificationTitle"),
|
2020-03-04 17:42:21 +01:00
|
|
|
showConfirmButton: true,
|
2023-05-02 18:46:03 +02:00
|
|
|
confirmButtonText: this.i18nService.t("ok"),
|
2020-03-04 17:42:21 +01:00
|
|
|
timer: 300000,
|
2021-12-21 15:43:35 +01:00
|
|
|
});
|
|
|
|
}
|
2022-03-18 00:01:22 +01:00
|
|
|
|
|
|
|
private async clearComponentStates() {
|
2022-04-13 01:15:46 +02:00
|
|
|
if (!(await this.stateService.getIsAuthenticated())) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-18 00:01:22 +01:00
|
|
|
await Promise.all([
|
|
|
|
this.stateService.setBrowserGroupingComponentState(null),
|
2022-11-22 14:30:33 +01:00
|
|
|
this.stateService.setBrowserVaultItemsComponentState(null),
|
2022-03-18 00:01:22 +01:00
|
|
|
this.stateService.setBrowserSendComponentState(null),
|
|
|
|
this.stateService.setBrowserSendTypeComponentState(null),
|
|
|
|
]);
|
|
|
|
}
|
2018-04-04 04:14:54 +02:00
|
|
|
}
|