mirror of
https://github.com/bitwarden/browser
synced 2026-02-26 09:33:22 +00:00
wire next achievement service to notifier service
This commit is contained in:
@@ -82,8 +82,7 @@ export class AchievementHub {
|
||||
|
||||
earned$(): Observable<Map<AchievementId, AchievementEarnedEvent>> {
|
||||
return this.achievementLog.pipe(
|
||||
filter((e) => isEarnedEvent(e)),
|
||||
map((e) => e as AchievementEarnedEvent),
|
||||
filter(isEarnedEvent),
|
||||
latestEarnedMetrics(),
|
||||
tap((m) => this.log.debug(m, "earned achievements update")),
|
||||
startWith(new Map<AchievementId, AchievementEarnedEvent>()),
|
||||
@@ -92,8 +91,7 @@ export class AchievementHub {
|
||||
|
||||
metrics$(): Observable<Map<MetricId, AchievementProgressEvent>> {
|
||||
return this.achievementLog.pipe(
|
||||
filter((e) => isProgressEvent(e)),
|
||||
map((e) => e as AchievementProgressEvent),
|
||||
filter(isProgressEvent),
|
||||
latestProgressMetrics(),
|
||||
tap((m) => this.log.debug(m, "achievement metrics update")),
|
||||
startWith(new Map<MetricId, AchievementProgressEvent>()),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Observable, OperatorFunction, map, pipe, withLatestFrom } from "rxjs";
|
||||
import { Observable, OperatorFunction, combineLatestWith, map, pipe, withLatestFrom } from "rxjs";
|
||||
|
||||
import { AchievementId, AchievementValidator, MetricId } from "./types";
|
||||
|
||||
@@ -7,12 +7,15 @@ import { AchievementId, AchievementValidator, MetricId } from "./types";
|
||||
function active(
|
||||
metrics$: Observable<ReadonlyMap<MetricId, number>>,
|
||||
earned$: Observable<ReadonlySet<AchievementId>>,
|
||||
// TODO: accept a configuration observable that completes without
|
||||
// emission when the user has opted out of achievements
|
||||
): OperatorFunction<AchievementValidator[], AchievementValidator[]> {
|
||||
return pipe(
|
||||
// TODO: accept a configuration observable that completes without
|
||||
// emission when the user has opted out of achievements
|
||||
withLatestFrom(metrics$, earned$),
|
||||
map(([monitors, metrics, earned]) => {
|
||||
// refresh when an achievement is earned, but not when metrics
|
||||
// update; this may cause metrics to overrun
|
||||
withLatestFrom(metrics$),
|
||||
combineLatestWith(earned$),
|
||||
map(([[monitors, metrics], earned]) => {
|
||||
// compute list of active achievements
|
||||
const active = monitors.filter((m) => {
|
||||
// 🧠 the filters could be lifted into a function argument & delivered
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import { filter, find, from, map, Observable } from "rxjs";
|
||||
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { AchievementHub } from "./achievement-hub";
|
||||
import { AchievementService as AchievementServiceAbstraction } from "./achievement.service.abstraction";
|
||||
import { EventStoreAbstraction } from "./event-store.abstraction.service";
|
||||
import {
|
||||
VaultItems_1_Added_Achievement,
|
||||
VaultItems_10_Added_Achievement,
|
||||
} from "./examples/achievements";
|
||||
import { isEarnedEvent, isProgressEvent } from "./meta";
|
||||
import {
|
||||
Achievement,
|
||||
AchievementEarnedEvent,
|
||||
AchievementEvent,
|
||||
AchievementId,
|
||||
AchievementProgressEvent,
|
||||
} from "./types";
|
||||
|
||||
// Service might be deprecated in favor of the AchievmentHub
|
||||
// The hub is currently missing a way of listing all achievements, finding by id, but that could be possibly done via the AchievementManager
|
||||
export class HubAchievementService implements AchievementServiceAbstraction {
|
||||
private _achievements: Achievement[] = [
|
||||
VaultItems_1_Added_Achievement,
|
||||
VaultItems_10_Added_Achievement,
|
||||
];
|
||||
|
||||
private _achievementsSubject = from(this._achievements);
|
||||
|
||||
earned$: Observable<AchievementEarnedEvent>;
|
||||
inProgress$: Observable<AchievementProgressEvent>;
|
||||
|
||||
achievementById$: (achievementId: string) => Observable<Achievement>;
|
||||
achievementsEarned$ = (userId: UserId) => { return this.earned$ };
|
||||
achievementsInProgress$ = (userId: UserId) => { return this.inProgress$ }
|
||||
|
||||
private achievementHub = new AchievementHub();
|
||||
|
||||
constructor() {
|
||||
this.achievementById$ = (achievementId: AchievementId) =>
|
||||
this._achievementsSubject.pipe(find((item: Achievement) => item.name === achievementId));
|
||||
|
||||
this.earned$ = this.achievementHub.new$().pipe(filter((event) => isEarnedEvent(event)), map((event) => {
|
||||
return event as AchievementEarnedEvent;
|
||||
}));
|
||||
|
||||
this.inProgress$ = this.achievementHub.new$().pipe(filter((event) => isProgressEvent(event)), map((event) => {
|
||||
return event as AchievementProgressEvent;
|
||||
}));
|
||||
}
|
||||
}
|
||||
100
libs/common/src/tools/achievements/next-achievement.service.ts
Normal file
100
libs/common/src/tools/achievements/next-achievement.service.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
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 { AchievementHub } from "./achievement-hub";
|
||||
import { AchievementService as AchievementServiceAbstraction } from "./achievement.service.abstraction";
|
||||
import { isEarnedEvent, isProgressEvent } from "./meta";
|
||||
import {
|
||||
Achievement,
|
||||
AchievementEarnedEvent,
|
||||
AchievementEvent,
|
||||
AchievementProgressEvent,
|
||||
AchievementValidator,
|
||||
} from "./types";
|
||||
import { ItemCreatedCountConfig } from "./validators/config/item-created-count-config";
|
||||
import { SendItemCreatedCountConfig } from "./validators/config/send-created-count-config";
|
||||
import { SendItemCreatedCountValidator } from "./validators/send-item-created-count-validator";
|
||||
import { VaultItemCreatedCountValidator } from "./validators/vault-item-created-count-validator";
|
||||
|
||||
export class NextAchievementService implements AchievementServiceAbstraction {
|
||||
constructor(private readonly eventLogs: UserEventLogProvider) {}
|
||||
|
||||
private hubs = new Map<string, AchievementHub>();
|
||||
|
||||
private getHub(account: Account) {
|
||||
if (!this.hubs.has(account.id)) {
|
||||
// FIXME: sync these from the server and load them
|
||||
const validators$ = new BehaviorSubject<AchievementValidator[]>([
|
||||
...VaultItemCreatedCountValidator.createValidators(ItemCreatedCountConfig.AllConfigs),
|
||||
...SendItemCreatedCountValidator.createValidators(SendItemCreatedCountConfig.AllConfigs),
|
||||
]);
|
||||
|
||||
// FIXME: load stored achievements
|
||||
const achievements$ = from([] as AchievementEvent[]);
|
||||
const events$ = this.eventLogs.monitor$(account);
|
||||
const hub = new AchievementHub(validators$, events$, achievements$);
|
||||
|
||||
this.hubs.set(account.id, hub);
|
||||
}
|
||||
|
||||
return this.hubs.get(account.id)!;
|
||||
}
|
||||
|
||||
private _achievements: Achievement[] = [
|
||||
...ItemCreatedCountConfig.AllConfigs,
|
||||
...SendItemCreatedCountConfig.AllConfigs,
|
||||
];
|
||||
|
||||
private _achievementsSubject = from(this._achievements);
|
||||
|
||||
achievementMap() {
|
||||
return new Map(this._achievements.map((a) => [a.achievement, a] as const));
|
||||
}
|
||||
|
||||
earnedStream$(account: Account, all: boolean = false) {
|
||||
const hub = this.getHub(account);
|
||||
if (all) {
|
||||
return hub.all$().pipe(filter(isEarnedEvent));
|
||||
} else {
|
||||
return hub.new$().pipe(filter(isEarnedEvent));
|
||||
}
|
||||
}
|
||||
|
||||
earnedMap$(account: Account) {
|
||||
return this.getHub(account).metrics$();
|
||||
}
|
||||
|
||||
progressStream$(account: Account, all: boolean = false) {
|
||||
const hub = this.getHub(account);
|
||||
if (all) {
|
||||
return hub.all$().pipe(filter(isProgressEvent));
|
||||
} else {
|
||||
return hub.new$().pipe(filter(isProgressEvent));
|
||||
}
|
||||
}
|
||||
|
||||
metricsMap$(account: Account) {
|
||||
return this.getHub(account).metrics$();
|
||||
}
|
||||
|
||||
achievementById$(achievementId: string): Observable<Achievement> {
|
||||
return this._achievementsSubject.pipe(
|
||||
find((item: Achievement) => item.name === achievementId),
|
||||
filter((f): f is Achievement => !!f),
|
||||
);
|
||||
}
|
||||
|
||||
earned$: Observable<AchievementEarnedEvent> = EMPTY;
|
||||
inProgress$: Observable<AchievementProgressEvent> = EMPTY;
|
||||
achievementsEarned$(userId: UserId): Observable<AchievementEarnedEvent> {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
achievementsInProgress$(userId: UserId): Observable<AchievementProgressEvent> {
|
||||
return EMPTY;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BehaviorSubject, SubjectLike, from, map, zip } from "rxjs";
|
||||
import { BehaviorSubject, Observable, SubjectLike, from, map, zip } from "rxjs";
|
||||
import { Primitive } from "type-fest";
|
||||
|
||||
import { Account } from "../../auth/abstractions/account.service";
|
||||
@@ -11,7 +11,8 @@ import { disabledSemanticLoggerProvider } from "./factory";
|
||||
import { SemanticLogger } from "./semantic-logger.abstraction";
|
||||
|
||||
export abstract class UserEventLogProvider {
|
||||
abstract create: (account: Account) => UserEventLogger;
|
||||
abstract capture: (account: Account) => UserEventLogger;
|
||||
abstract monitor$: (account: Account) => Observable<UserActionEvent>;
|
||||
}
|
||||
|
||||
type BaselineType = Omit<ServiceFormat & UserFormat, "@timestamp">;
|
||||
|
||||
Reference in New Issue
Block a user