diff --git a/libs/common/src/tools/achievements/next-achievement.service.ts b/libs/common/src/tools/achievements/next-achievement.service.ts index 7782b1369a2..ce886729940 100644 --- a/libs/common/src/tools/achievements/next-achievement.service.ts +++ b/libs/common/src/tools/achievements/next-achievement.service.ts @@ -3,7 +3,7 @@ import { BehaviorSubject, EMPTY, filter, find, from, Observable } from "rxjs"; import { UserId } from "@bitwarden/common/types/guid"; import { Account } from "../../auth/abstractions/account.service"; -import { UserEventLogProvider } from "../log/logger"; +import { UserEventCollector } from "../log/user-event-collector"; import { AchievementHub } from "./achievement-hub"; import { AchievementService as AchievementServiceAbstraction } from "./achievement.service.abstraction"; @@ -21,7 +21,7 @@ import { SendItemCreatedCountValidator } from "./validators/send-item-created-co import { VaultItemCreatedCountValidator } from "./validators/vault-item-created-count-validator"; export class NextAchievementService implements AchievementServiceAbstraction { - constructor(private readonly eventLogs: UserEventLogProvider) {} + constructor(private readonly eventLogs: UserEventCollector) {} private hubs = new Map(); @@ -35,7 +35,7 @@ export class NextAchievementService implements AchievementServiceAbstraction { // FIXME: load stored achievements const achievements$ = from([] as AchievementEvent[]); - const events$ = this.eventLogs.monitor$(account); + const events$ = this.eventLogs.events$(account); const hub = new AchievementHub(validators$, events$, achievements$); this.hubs.set(account.id, hub); diff --git a/libs/common/src/tools/log/default-user-event-collector.ts b/libs/common/src/tools/log/default-user-event-collector.ts new file mode 100644 index 00000000000..411cfd92d3a --- /dev/null +++ b/libs/common/src/tools/log/default-user-event-collector.ts @@ -0,0 +1,48 @@ +import { Observable, Subject } from "rxjs"; + +import { Account } from "../../auth/abstractions/account.service"; +import { AppIdService } from "../../platform/abstractions/app-id.service"; +import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service"; +import { UserId } from "../../types/guid"; +import { UserActionEvent } from "../achievements/types"; + +import { UserEventMonitor } from "./user-event-monitor"; + +export class DefaultUserEventCollector { + private eventStreams = new Map>(); + + constructor( + private idService: AppIdService, + private utilService: PlatformUtilsService, + ) {} + + private getStream(account: Account) { + let events$ = this.eventStreams.get(account.id); + if (!events$) { + // FIXME: this should include a ring buffer and spool + // when the buffer is full so that user action events + // are not lost. Don't forget encryption... + events$ = new Subject(); + this.eventStreams.set(account.id, events$); + } + + return events$; + } + + monitor(account: Account): UserEventMonitor { + const events$ = this.getStream(account); + + const logger = new UserEventMonitor( + this.idService, + this.utilService, + account, + Date.now, + events$, + ); + return logger; + } + + events$(account: Account): Observable { + return this.getStream(account).asObservable(); + } +} diff --git a/libs/common/src/tools/log/user-event-collector.ts b/libs/common/src/tools/log/user-event-collector.ts new file mode 100644 index 00000000000..a6cac7b9171 --- /dev/null +++ b/libs/common/src/tools/log/user-event-collector.ts @@ -0,0 +1,11 @@ +import { Observable } from "rxjs"; + +import { Account } from "../../auth/abstractions/account.service"; +import { UserActionEvent } from "../achievements/types"; + +import { UserEventMonitor } from "./user-event-monitor"; + +export abstract class UserEventCollector { + abstract monitor: (account: Account) => UserEventMonitor; + abstract events$: (account: Account) => Observable; +} diff --git a/libs/common/src/tools/log/logger.ts b/libs/common/src/tools/log/user-event-monitor.ts similarity index 91% rename from libs/common/src/tools/log/logger.ts rename to libs/common/src/tools/log/user-event-monitor.ts index 50055c284e3..7b7d32d0a32 100644 --- a/libs/common/src/tools/log/logger.ts +++ b/libs/common/src/tools/log/user-event-monitor.ts @@ -1,4 +1,4 @@ -import { BehaviorSubject, Observable, SubjectLike, from, map, zip } from "rxjs"; +import { BehaviorSubject, SubjectLike, from, map, zip } from "rxjs"; import { Primitive } from "type-fest"; import { Account } from "../../auth/abstractions/account.service"; @@ -10,11 +10,6 @@ import { ServiceFormat, UserFormat, EcsEventType } from "./ecs-format"; import { disabledSemanticLoggerProvider } from "./factory"; import { SemanticLogger } from "./semantic-logger.abstraction"; -export abstract class UserEventLogProvider { - abstract capture: (account: Account) => UserEventLogger; - abstract monitor$: (account: Account) => Observable; -} - type BaselineType = Omit; export type EventInfo = { @@ -23,7 +18,7 @@ export type EventInfo = { tags?: Array; }; -export class UserEventLogger { +export class UserEventMonitor { constructor( idService: AppIdService, utilService: PlatformUtilsService, diff --git a/libs/common/src/tools/providers.ts b/libs/common/src/tools/providers.ts index 37e98ffe8e8..1ffc2ffb123 100644 --- a/libs/common/src/tools/providers.ts +++ b/libs/common/src/tools/providers.ts @@ -2,7 +2,7 @@ import { PolicyService } from "../admin-console/abstractions/policy/policy.servi import { ExtensionService } from "./extension/extension.service"; import { LogProvider } from "./log"; -import { UserEventLogProvider } from "./log/logger"; +import { UserEventCollector } from "./log/user-event-monitor"; /** Provides access to commonly-used cross-cutting services. */ export type SystemServiceProvider = { @@ -15,5 +15,5 @@ export type SystemServiceProvider = { /** Event monitoring and diagnostic interfaces */ readonly log: LogProvider; - readonly event: UserEventLogProvider; + readonly event: UserEventCollector; }; diff --git a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts index c05e9711b36..38710ea26dc 100644 --- a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts +++ b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts @@ -6,7 +6,7 @@ import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { UserEventLogProvider } from "@bitwarden/common/tools/log/logger"; +import { UserEventCollector } from "@bitwarden/common/tools/log/user-event-collector"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; @@ -24,7 +24,7 @@ export class DefaultCipherFormService implements CipherFormService { private cipherService: CipherService = inject(CipherService); private accountService: AccountService = inject(AccountService); private apiService: ApiService = inject(ApiService); - private system = inject(UserEventLogProvider); + private collector = inject(UserEventCollector); async decryptCipher(cipher: Cipher): Promise { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); @@ -44,7 +44,7 @@ export class DefaultCipherFormService implements CipherFormService { null, config.originalCipher ?? null, ); - const event = this.system.create(activeUser); + const event = this.collector.monitor(activeUser); const labels = { "vault-item-type": CipherType[cipher.type], "vault-item-uri-quantity": cipher.type === CipherType.Login ? cipher.login.uris.length : null,