Add account activity tracking to account service
This commit is contained in:
parent
e7678de6e7
commit
403b2c8c0c
|
@ -30,6 +30,7 @@ export function accountInfoEqual(a: AccountInfo, b: AccountInfo) {
|
|||
export abstract class AccountService {
|
||||
accounts$: Observable<Record<UserId, AccountInfo>>;
|
||||
activeAccount$: Observable<{ id: UserId | undefined } & AccountInfo>;
|
||||
accountActivity$: Observable<Record<UserId, Date>>;
|
||||
/**
|
||||
* Updates the `accounts$` observable with the new account data.
|
||||
* @param userId
|
||||
|
@ -64,6 +65,12 @@ export abstract class AccountService {
|
|||
* @param userId
|
||||
*/
|
||||
abstract clean(userId: UserId): Promise<void>;
|
||||
/**
|
||||
* Updates the given user's last activity time.
|
||||
* @param userId
|
||||
* @param lastActivity
|
||||
*/
|
||||
abstract setAccountActivity(userId: UserId, lastActivity: Date): Promise<void>;
|
||||
}
|
||||
|
||||
export abstract class InternalAccountService extends AccountService {
|
||||
|
|
|
@ -12,6 +12,7 @@ import { AccountInfo, accountInfoEqual } from "../abstractions/account.service";
|
|||
import {
|
||||
ACCOUNT_ACCOUNTS,
|
||||
ACCOUNT_ACTIVE_ACCOUNT_ID,
|
||||
ACCOUNT_ACTIVITY,
|
||||
AccountServiceImplementation,
|
||||
} from "./account.service";
|
||||
|
||||
|
@ -215,6 +216,16 @@ describe("accountService", () => {
|
|||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("removes account activity of the given user", async () => {
|
||||
const state = globalStateProvider.getFake(ACCOUNT_ACTIVITY);
|
||||
state.stateSubject.next({ [userId]: new Date() });
|
||||
|
||||
await sut.clean(userId);
|
||||
const activityState = await firstValueFrom(state.state$);
|
||||
|
||||
expect(activityState).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe("switchAccount", () => {
|
||||
|
@ -235,4 +246,30 @@ describe("accountService", () => {
|
|||
expect(sut.switchAccount("unknown" as UserId)).rejects.toThrowError("Account does not exist");
|
||||
});
|
||||
});
|
||||
|
||||
describe("setAccountActivity", () => {
|
||||
it("should update the account activity", async () => {
|
||||
await sut.setAccountActivity(userId, new Date());
|
||||
const state = globalStateProvider.getFake(ACCOUNT_ACTIVITY);
|
||||
|
||||
expect(state.nextMock).toHaveBeenCalledWith({ [userId]: expect.any(Date) });
|
||||
});
|
||||
|
||||
it("should not update if the activity is the same", async () => {
|
||||
const date = new Date();
|
||||
const state = globalStateProvider.getFake(ACCOUNT_ACTIVITY);
|
||||
state.stateSubject.next({ [userId]: date });
|
||||
|
||||
await sut.setAccountActivity(userId, date);
|
||||
|
||||
expect(state.nextMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should update accountActivity$ with the new activity", async () => {
|
||||
await sut.setAccountActivity(userId, new Date());
|
||||
const currentState = await firstValueFrom(sut.accountActivity$);
|
||||
|
||||
expect(currentState[userId]).toBeInstanceOf(Date);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,6 +26,11 @@ export const ACCOUNT_ACCOUNTS = KeyDefinition.record<AccountInfo, UserId>(
|
|||
export const ACCOUNT_ACTIVE_ACCOUNT_ID = new KeyDefinition(ACCOUNT_DISK, "activeAccountId", {
|
||||
deserializer: (id: UserId) => id,
|
||||
});
|
||||
|
||||
export const ACCOUNT_ACTIVITY = KeyDefinition.record<Date, UserId>(ACCOUNT_DISK, "activity", {
|
||||
deserializer: (activity) => new Date(activity),
|
||||
});
|
||||
|
||||
const loggedOutInfo: AccountInfo = {
|
||||
email: "",
|
||||
emailVerified: false,
|
||||
|
@ -40,6 +45,7 @@ export class AccountServiceImplementation implements InternalAccountService {
|
|||
|
||||
accounts$;
|
||||
activeAccount$;
|
||||
accountActivity$;
|
||||
|
||||
constructor(
|
||||
private messagingService: MessagingService,
|
||||
|
@ -58,6 +64,7 @@ export class AccountServiceImplementation implements InternalAccountService {
|
|||
distinctUntilChanged((a, b) => a?.id === b?.id && accountInfoEqual(a, b)),
|
||||
shareReplay({ bufferSize: 1, refCount: false }),
|
||||
);
|
||||
this.accountActivity$ = this.globalStateProvider.get(ACCOUNT_ACTIVITY).state$;
|
||||
}
|
||||
|
||||
async addAccount(userId: UserId, accountData: AccountInfo): Promise<void> {
|
||||
|
@ -82,6 +89,7 @@ export class AccountServiceImplementation implements InternalAccountService {
|
|||
|
||||
async clean(userId: UserId) {
|
||||
await this.setAccountInfo(userId, loggedOutInfo);
|
||||
await this.removeAccountActivity(userId);
|
||||
}
|
||||
|
||||
async switchAccount(userId: UserId): Promise<void> {
|
||||
|
@ -107,6 +115,19 @@ export class AccountServiceImplementation implements InternalAccountService {
|
|||
);
|
||||
}
|
||||
|
||||
async setAccountActivity(userId: UserId, date: Date): Promise<void> {
|
||||
await this.globalStateProvider.get(ACCOUNT_ACTIVITY).update(
|
||||
(activity) => {
|
||||
activity ??= {};
|
||||
activity[userId] = date;
|
||||
return activity;
|
||||
},
|
||||
{
|
||||
shouldUpdate: (activity) => activity?.[userId] != date,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: update to use our own account status settings. Requires inverting direction of state service accounts flow
|
||||
async delete(): Promise<void> {
|
||||
try {
|
||||
|
@ -139,4 +160,16 @@ export class AccountServiceImplementation implements InternalAccountService {
|
|||
},
|
||||
);
|
||||
}
|
||||
|
||||
private async removeAccountActivity(userId: UserId): Promise<void> {
|
||||
await this.globalStateProvider.get(ACCOUNT_ACTIVITY).update(
|
||||
(activity) => {
|
||||
delete activity?.[userId];
|
||||
return activity;
|
||||
},
|
||||
{
|
||||
shouldUpdate: (activity) => activity?.[userId] != null,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue