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, firstValueFrom, Subject, takeUntil } from "rxjs"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Account } from "@bitwarden/common/platform/models/domain/account"; import { UserId } from "@bitwarden/common/types/guid"; type ActiveAccount = { id: string; name: string; email: string; avatarColor: string; server: string; }; type InactiveAccount = ActiveAccount & { authenticationStatus: AuthenticationStatus; }; @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 { activeAccount?: ActiveAccount; inactiveAccounts: { [userId: string]: InactiveAccount } = {}; authStatus = AuthenticationStatus; isOpen = false; overlayPosition: ConnectedPosition[] = [ { originX: "end", originY: "bottom", overlayX: "end", overlayY: "top", }, ]; private destroy$ = new Subject(); get showSwitcher() { const userIsInAVault = !Utils.isNullOrWhitespace(this.activeAccount?.email); const userIsAddingAnAdditionalAccount = Object.keys(this.inactiveAccounts).length > 0; return userIsInAVault || userIsAddingAnAdditionalAccount; } get numberOfAccounts() { if (this.inactiveAccounts == null) { this.isOpen = false; return 0; } return Object.keys(this.inactiveAccounts).length; } constructor( private stateService: StateService, private authService: AuthService, private avatarService: AvatarService, private messagingService: MessagingService, private router: Router, private tokenService: TokenService, private environmentService: EnvironmentService, ) {} async ngOnInit(): Promise { this.stateService.accounts$ .pipe( concatMap(async (accounts: { [userId: string]: Account }) => { this.inactiveAccounts = await this.createInactiveAccounts(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 firstValueFrom(this.avatarService.avatarColor$), server: await this.environmentService.getHost(), }; } 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.setRememberedEmail(null); await this.router.navigate(["/login"]); await this.stateService.setActiveUser(null); } private async createInactiveAccounts(baseAccounts: { [userId: string]: Account; }): Promise<{ [userId: string]: InactiveAccount }> { const inactiveAccounts: { [userId: string]: InactiveAccount } = {}; for (const userId in baseAccounts) { if (userId == null || userId === (await this.stateService.getUserId())) { continue; } inactiveAccounts[userId] = { id: userId, name: baseAccounts[userId].profile.name, email: baseAccounts[userId].profile.email, authenticationStatus: await this.authService.getAuthStatus(userId), avatarColor: await firstValueFrom(this.avatarService.getUserAvatarColor$(userId as UserId)), server: await this.environmentService.getHost(userId), }; } return inactiveAccounts; } }