mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
[PM-6688] Use AccountService as account source (#8893)
* Use account service to track accounts and active account * Remove state service active account Observables. * Add email verified to account service * Do not store account info on logged out accounts * Add account activity tracking to account service * Use last account activity from account service * migrate or replicate account service data * Add `AccountActivityService` that handles storing account last active data * Move active and next active user to account service * Remove authenticated accounts from state object * Fold account activity into account service * Fix builds * Fix desktop app switch * Fix logging out non active user * Expand helper to handle new authenticated accounts location * Prefer view observable to tons of async pipes * Fix `npm run test:types` * Correct user activity sorting test * Be more precise about log out messaging * Fix dev compare errors All stored values are serializable, the next step wasn't necessary and was erroring on some types that lack `toString`. * If the account in unlocked on load of lock component, navigate away from lock screen * Handle no users case for auth service statuses * Specify account to switch to * Filter active account out of inactive accounts * Prefer constructor init * Improve comparator * Use helper methods internally * Fixup component tests * Clarify name * Ensure accounts object has only valid userIds * Capitalize const values * Prefer descriptive, single-responsibility guards * Update libs/common/src/state-migrations/migrate.ts Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> * Fix merge * Add user Id validation activity for undefined was being set, which was resulting in requests for the auth status of `"undefined"` (string) userId, due to key enumeration. These changes stop that at both locations, as well as account add for good measure. --------- Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { ReplaySubject } from "rxjs";
|
||||
import { ReplaySubject, combineLatest, map } from "rxjs";
|
||||
|
||||
import { AccountInfo, AccountService } from "../src/auth/abstractions/account.service";
|
||||
import { UserId } from "../src/types/guid";
|
||||
@@ -7,15 +7,20 @@ import { UserId } from "../src/types/guid";
|
||||
export function mockAccountServiceWith(
|
||||
userId: UserId,
|
||||
info: Partial<AccountInfo> = {},
|
||||
activity: Record<UserId, Date> = {},
|
||||
): FakeAccountService {
|
||||
const fullInfo: AccountInfo = {
|
||||
...info,
|
||||
...{
|
||||
name: "name",
|
||||
email: "email",
|
||||
emailVerified: true,
|
||||
},
|
||||
};
|
||||
const service = new FakeAccountService({ [userId]: fullInfo });
|
||||
|
||||
const fullActivity = { [userId]: new Date(), ...activity };
|
||||
|
||||
const service = new FakeAccountService({ [userId]: fullInfo }, fullActivity);
|
||||
service.activeAccountSubject.next({ id: userId, ...fullInfo });
|
||||
return service;
|
||||
}
|
||||
@@ -26,17 +31,46 @@ export class FakeAccountService implements AccountService {
|
||||
accountsSubject = new ReplaySubject<Record<UserId, AccountInfo>>(1);
|
||||
// eslint-disable-next-line rxjs/no-exposed-subjects -- test class
|
||||
activeAccountSubject = new ReplaySubject<{ id: UserId } & AccountInfo>(1);
|
||||
// eslint-disable-next-line rxjs/no-exposed-subjects -- test class
|
||||
accountActivitySubject = new ReplaySubject<Record<UserId, Date>>(1);
|
||||
private _activeUserId: UserId;
|
||||
get activeUserId() {
|
||||
return this._activeUserId;
|
||||
}
|
||||
accounts$ = this.accountsSubject.asObservable();
|
||||
activeAccount$ = this.activeAccountSubject.asObservable();
|
||||
accountActivity$ = this.accountActivitySubject.asObservable();
|
||||
get sortedUserIds$() {
|
||||
return this.accountActivity$.pipe(
|
||||
map((activity) => {
|
||||
return Object.entries(activity)
|
||||
.map(([userId, lastActive]: [UserId, Date]) => ({ userId, lastActive }))
|
||||
.sort((a, b) => a.lastActive.getTime() - b.lastActive.getTime())
|
||||
.map((a) => a.userId);
|
||||
}),
|
||||
);
|
||||
}
|
||||
get nextUpAccount$() {
|
||||
return combineLatest([this.accounts$, this.activeAccount$, this.sortedUserIds$]).pipe(
|
||||
map(([accounts, activeAccount, sortedUserIds]) => {
|
||||
const nextId = sortedUserIds.find((id) => id !== activeAccount?.id && accounts[id] != null);
|
||||
return nextId ? { id: nextId, ...accounts[nextId] } : null;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
constructor(initialData: Record<UserId, AccountInfo>) {
|
||||
constructor(initialData: Record<UserId, AccountInfo>, accountActivity?: Record<UserId, Date>) {
|
||||
this.accountsSubject.next(initialData);
|
||||
this.activeAccountSubject.subscribe((data) => (this._activeUserId = data?.id));
|
||||
this.activeAccountSubject.next(null);
|
||||
this.accountActivitySubject.next(accountActivity);
|
||||
}
|
||||
setAccountActivity(userId: UserId, lastActivity: Date): Promise<void> {
|
||||
this.accountActivitySubject.next({
|
||||
...this.accountActivitySubject["_buffer"][0],
|
||||
[userId]: lastActivity,
|
||||
});
|
||||
return this.mock.setAccountActivity(userId, lastActivity);
|
||||
}
|
||||
|
||||
async addAccount(userId: UserId, accountData: AccountInfo): Promise<void> {
|
||||
@@ -53,10 +87,27 @@ export class FakeAccountService implements AccountService {
|
||||
await this.mock.setAccountEmail(userId, email);
|
||||
}
|
||||
|
||||
async setAccountEmailVerified(userId: UserId, emailVerified: boolean): Promise<void> {
|
||||
await this.mock.setAccountEmailVerified(userId, emailVerified);
|
||||
}
|
||||
|
||||
async switchAccount(userId: UserId): Promise<void> {
|
||||
const next =
|
||||
userId == null ? null : { id: userId, ...this.accountsSubject["_buffer"]?.[0]?.[userId] };
|
||||
this.activeAccountSubject.next(next);
|
||||
await this.mock.switchAccount(userId);
|
||||
}
|
||||
|
||||
async clean(userId: UserId): Promise<void> {
|
||||
const current = this.accountsSubject["_buffer"][0] ?? {};
|
||||
const updated = { ...current, [userId]: loggedOutInfo };
|
||||
this.accountsSubject.next(updated);
|
||||
await this.mock.clean(userId);
|
||||
}
|
||||
}
|
||||
|
||||
const loggedOutInfo: AccountInfo = {
|
||||
name: undefined,
|
||||
email: "",
|
||||
emailVerified: false,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user