mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 00:03:56 +00:00
158 lines
5.4 KiB
TypeScript
158 lines
5.4 KiB
TypeScript
import { inject, Injectable } from "@angular/core";
|
|
import { combineLatest, map, Observable, shareReplay } from "rxjs";
|
|
|
|
import { UserKeyDefinition, NUDGES_DISK } from "@bitwarden/common/platform/state";
|
|
import { UserId } from "@bitwarden/common/types/guid";
|
|
import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values";
|
|
|
|
import {
|
|
NewAccountNudgeService,
|
|
HasItemsNudgeService,
|
|
EmptyVaultNudgeService,
|
|
NewItemNudgeService,
|
|
AccountSecurityNudgeService,
|
|
VaultSettingsImportNudgeService,
|
|
} from "./custom-nudges-services";
|
|
import { DefaultSingleNudgeService, SingleNudgeService } from "./default-single-nudge.service";
|
|
|
|
export type NudgeStatus = {
|
|
hasBadgeDismissed: boolean;
|
|
hasSpotlightDismissed: boolean;
|
|
};
|
|
|
|
/**
|
|
* Enum to list the various nudge types, to be used by components/badges to show/hide the nudge
|
|
*/
|
|
export const NudgeType = {
|
|
/** Nudge to show when user has no items in their vault */
|
|
EmptyVaultNudge: "empty-vault-nudge",
|
|
VaultSettingsImportNudge: "vault-settings-import-nudge",
|
|
HasVaultItems: "has-vault-items",
|
|
AutofillNudge: "autofill-nudge",
|
|
AccountSecurity: "account-security",
|
|
DownloadBitwarden: "download-bitwarden",
|
|
NewLoginItemStatus: "new-login-item-status",
|
|
NewCardItemStatus: "new-card-item-status",
|
|
NewIdentityItemStatus: "new-identity-item-status",
|
|
NewNoteItemStatus: "new-note-item-status",
|
|
NewSshItemStatus: "new-ssh-item-status",
|
|
GeneratorNudgeStatus: "generator-nudge-status",
|
|
} as const;
|
|
|
|
export type NudgeType = UnionOfValues<typeof NudgeType>;
|
|
|
|
export const NUDGE_DISMISSED_DISK_KEY = new UserKeyDefinition<
|
|
Partial<Record<NudgeType, NudgeStatus>>
|
|
>(NUDGES_DISK, "vaultNudgeDismissed", {
|
|
deserializer: (nudge) => nudge,
|
|
clearOn: [], // Do not clear dismissals
|
|
});
|
|
|
|
@Injectable({
|
|
providedIn: "root",
|
|
})
|
|
export class NudgesService {
|
|
private newItemNudgeService = inject(NewItemNudgeService);
|
|
private newAcctNudgeService = inject(NewAccountNudgeService);
|
|
|
|
/**
|
|
* Custom nudge services to use for specific nudge types
|
|
* Each nudge type can have its own service to determine when to show the nudge
|
|
* @private
|
|
*/
|
|
private customNudgeServices: Partial<Record<NudgeType, SingleNudgeService>> = {
|
|
[NudgeType.HasVaultItems]: inject(HasItemsNudgeService),
|
|
[NudgeType.EmptyVaultNudge]: inject(EmptyVaultNudgeService),
|
|
[NudgeType.VaultSettingsImportNudge]: inject(VaultSettingsImportNudgeService),
|
|
[NudgeType.AccountSecurity]: inject(AccountSecurityNudgeService),
|
|
[NudgeType.AutofillNudge]: this.newAcctNudgeService,
|
|
[NudgeType.DownloadBitwarden]: this.newAcctNudgeService,
|
|
[NudgeType.GeneratorNudgeStatus]: this.newAcctNudgeService,
|
|
[NudgeType.NewLoginItemStatus]: this.newItemNudgeService,
|
|
[NudgeType.NewCardItemStatus]: this.newItemNudgeService,
|
|
[NudgeType.NewIdentityItemStatus]: this.newItemNudgeService,
|
|
[NudgeType.NewNoteItemStatus]: this.newItemNudgeService,
|
|
[NudgeType.NewSshItemStatus]: this.newItemNudgeService,
|
|
};
|
|
|
|
/**
|
|
* Default nudge service to use when no custom service is available
|
|
* Simply stores the dismissed state in the user's state
|
|
* @private
|
|
*/
|
|
private defaultNudgeService = inject(DefaultSingleNudgeService);
|
|
|
|
private getNudgeService(nudge: NudgeType): SingleNudgeService {
|
|
return this.customNudgeServices[nudge] ?? this.defaultNudgeService;
|
|
}
|
|
|
|
/**
|
|
* Check if a nudge Spotlight should be shown to the user
|
|
* @param nudge
|
|
* @param userId
|
|
*/
|
|
showNudgeSpotlight$(nudge: NudgeType, userId: UserId): Observable<boolean> {
|
|
return this.getNudgeService(nudge)
|
|
.nudgeStatus$(nudge, userId)
|
|
.pipe(map((nudgeStatus) => !nudgeStatus.hasSpotlightDismissed));
|
|
}
|
|
|
|
/**
|
|
* Check if a nudge Badge should be shown to the user
|
|
* @param nudge
|
|
* @param userId
|
|
*/
|
|
showNudgeBadge$(nudge: NudgeType, userId: UserId): Observable<boolean> {
|
|
return this.getNudgeService(nudge)
|
|
.nudgeStatus$(nudge, userId)
|
|
.pipe(map((nudgeStatus) => !nudgeStatus.hasBadgeDismissed));
|
|
}
|
|
|
|
/**
|
|
* Check if a nudge should be shown to the user
|
|
* @param nudge
|
|
* @param userId
|
|
*/
|
|
showNudgeStatus$(nudge: NudgeType, userId: UserId) {
|
|
return this.getNudgeService(nudge).nudgeStatus$(nudge, userId);
|
|
}
|
|
|
|
/**
|
|
* Dismiss a nudge for the user so that it is not shown again
|
|
* @param nudge
|
|
* @param userId
|
|
*/
|
|
async dismissNudge(nudge: NudgeType, userId: UserId, onlyBadge: boolean = false) {
|
|
const dismissedStatus = onlyBadge
|
|
? { hasBadgeDismissed: true, hasSpotlightDismissed: false }
|
|
: { hasBadgeDismissed: true, hasSpotlightDismissed: true };
|
|
await this.getNudgeService(nudge).setNudgeStatus(nudge, dismissedStatus, userId);
|
|
}
|
|
|
|
/**
|
|
* Check if there are any active badges for the user to show Berry notification in Tabs
|
|
* @param userId
|
|
*/
|
|
hasActiveBadges$(userId: UserId): Observable<boolean> {
|
|
// Add more nudge types here if they have the settings badge feature
|
|
const nudgeTypes = [
|
|
NudgeType.EmptyVaultNudge,
|
|
NudgeType.DownloadBitwarden,
|
|
NudgeType.AutofillNudge,
|
|
];
|
|
|
|
const nudgeTypesWithBadge$ = nudgeTypes.map((nudge) => {
|
|
return this.getNudgeService(nudge)
|
|
.nudgeStatus$(nudge, userId)
|
|
.pipe(
|
|
map((status) => !status?.hasBadgeDismissed),
|
|
shareReplay({ refCount: false, bufferSize: 1 }),
|
|
);
|
|
});
|
|
|
|
return combineLatest(nudgeTypesWithBadge$).pipe(
|
|
map((results) => results.some((result) => result === true)),
|
|
);
|
|
}
|
|
}
|