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(); 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 { 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; } }