bitwarden-estensione-browser/apps/desktop/src/app/layout/account-switcher.component.ts

176 lines
5.1 KiB
TypeScript

import { animate, state, style, transition, trigger } from "@angular/animations";
import { ConnectedPosition } from "@angular/cdk/overlay";
import { Component, OnDestroy, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { concatMap, Subject, takeUntil } from "rxjs";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { Utils } from "@bitwarden/common/misc/utils";
import { Account } from "@bitwarden/common/models/domain/account";
type ActiveAccount = {
id: string;
name: string;
email: string;
avatarColor: string;
};
export class SwitcherAccount extends Account {
get serverUrl() {
return this.removeWebProtocolFromString(
this.settings?.environmentUrls?.base ??
this.settings?.environmentUrls.api ??
"https://bitwarden.com"
);
}
avatarColor: string;
private removeWebProtocolFromString(urlString: string) {
const regex = /http(s)?(:)?(\/\/)?|(\/\/)?(www\.)?/g;
return urlString.replace(regex, "");
}
}
@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, OnDestroy {
private destroy$ = new Subject<void>();
isOpen = false;
accounts: { [userId: string]: SwitcherAccount } = {};
activeAccount?: ActiveAccount;
serverUrl: string;
authStatus = AuthenticationStatus;
overlayPostition: ConnectedPosition[] = [
{
originX: "end",
originY: "bottom",
overlayX: "end",
overlayY: "top",
},
];
get showSwitcher() {
const userIsInAVault = !Utils.isNullOrWhitespace(this.activeAccount?.email);
const userIsAddingAnAdditionalAccount = Object.keys(this.accounts).length > 0;
return userIsInAVault || userIsAddingAnAdditionalAccount;
}
get numberOfAccounts() {
if (this.accounts == null) {
this.isOpen = false;
return 0;
}
return Object.keys(this.accounts).length;
}
constructor(
private stateService: StateService,
private authService: AuthService,
private messagingService: MessagingService,
private router: Router,
private tokenService: TokenService
) {}
async ngOnInit(): Promise<void> {
this.stateService.accounts$
.pipe(
concatMap(async (accounts: { [userId: string]: Account }) => {
for (const userId in accounts) {
accounts[userId].profile.authenticationStatus = await this.authService.getAuthStatus(
userId
);
}
this.accounts = await this.createSwitcherAccounts(accounts);
try {
this.activeAccount = {
id: await this.tokenService.getUserId(),
name: (await this.tokenService.getName()) ?? (await this.tokenService.getEmail()),
email: await this.tokenService.getEmail(),
avatarColor: await this.stateService.getAvatarColor(),
};
} catch {
this.activeAccount = undefined;
}
}),
takeUntil(this.destroy$)
)
.subscribe();
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
toggle() {
this.isOpen = !this.isOpen;
}
close() {
this.isOpen = false;
}
async switch(userId: string) {
this.close();
this.messagingService.send("switchAccount", { userId: userId });
}
async addAccount() {
this.close();
await this.stateService.setActiveUser(null);
await this.stateService.setRememberedEmail(null);
this.router.navigate(["/login"]);
}
private async createSwitcherAccounts(baseAccounts: {
[userId: string]: Account;
}): Promise<{ [userId: string]: SwitcherAccount }> {
const switcherAccounts: { [userId: string]: SwitcherAccount } = {};
for (const userId in baseAccounts) {
if (userId == null || userId === (await this.stateService.getUserId())) {
continue;
}
// environmentUrls are stored on disk and must be retrieved seperatly from the in memory state offered from subscribing to accounts
baseAccounts[userId].settings.environmentUrls = await this.stateService.getEnvironmentUrls({
userId: userId,
});
switcherAccounts[userId] = new SwitcherAccount(baseAccounts[userId]);
switcherAccounts[userId].avatarColor = await this.stateService.getAvatarColor({
userId: userId,
});
}
return switcherAccounts;
}
}