1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-10-22 07:50:04 +02:00

fix service dropdown not selecting a forwarder

This commit is contained in:
✨ Audrey ✨ 2024-10-18 14:11:29 -04:00
parent 994877c005
commit 11dc419780
No known key found for this signature in database
GPG Key ID: 0CF8B4C0D9088B97

View File

@ -25,8 +25,6 @@ import {
CredentialAlgorithm, CredentialAlgorithm,
CredentialCategory, CredentialCategory,
CredentialGeneratorService, CredentialGeneratorService,
EmailAlgorithm,
ForwarderIntegration,
GeneratedCredential, GeneratedCredential,
Generators, Generators,
getForwarderConfiguration, getForwarderConfiguration,
@ -34,21 +32,14 @@ import {
isForwarderIntegration, isForwarderIntegration,
isPasswordAlgorithm, isPasswordAlgorithm,
isUsernameAlgorithm, isUsernameAlgorithm,
PasswordAlgorithm,
toCredentialGeneratorConfiguration, toCredentialGeneratorConfiguration,
UsernameAlgorithm,
} from "@bitwarden/generator-core"; } from "@bitwarden/generator-core";
/** root category that drills into username and email categories */ // constants used to identify navigation selections that are not
// generator algorithms
const IDENTIFIER = "identifier"; const IDENTIFIER = "identifier";
/** options available for the top-level navigation */
type RootNavValue = PasswordAlgorithm | typeof IDENTIFIER;
const FORWARDER = "forwarder"; const FORWARDER = "forwarder";
type UsernameNavValue = UsernameAlgorithm | EmailAlgorithm | typeof FORWARDER;
const NONE_SELECTED = "none"; const NONE_SELECTED = "none";
type ForwarderNavValue = ForwarderIntegration | typeof NONE_SELECTED;
@Component({ @Component({
selector: "tools-credential-generator", selector: "tools-credential-generator",
@ -73,11 +64,11 @@ export class CredentialGeneratorComponent implements OnInit, OnDestroy {
@Output() @Output()
readonly onGenerated = new EventEmitter<GeneratedCredential>(); readonly onGenerated = new EventEmitter<GeneratedCredential>();
protected root$ = new BehaviorSubject<{ nav: RootNavValue }>({ protected root$ = new BehaviorSubject<{ nav: string }>({
nav: null, nav: null,
}); });
protected onRootChanged(value: { nav: RootNavValue }) { protected onRootChanged(value: { nav: string }) {
// prevent subscription cycle // prevent subscription cycle
if (this.root$.value.nav !== value.nav) { if (this.root$.value.nav !== value.nav) {
this.zone.run(() => { this.zone.run(() => {
@ -87,11 +78,11 @@ export class CredentialGeneratorComponent implements OnInit, OnDestroy {
} }
protected username = this.formBuilder.group({ protected username = this.formBuilder.group({
nav: [null as UsernameNavValue], nav: [null as string],
}); });
protected forwarder = this.formBuilder.group({ protected forwarder = this.formBuilder.group({
nav: [null as ForwarderNavValue], nav: [null as string],
}); });
async ngOnInit() { async ngOnInit() {
@ -112,11 +103,11 @@ export class CredentialGeneratorComponent implements OnInit, OnDestroy {
.pipe( .pipe(
map((algorithms) => { map((algorithms) => {
const usernames = algorithms.filter((a) => !isForwarderIntegration(a.id)); const usernames = algorithms.filter((a) => !isForwarderIntegration(a.id));
const usernameOptions = this.toOptions(usernames) as Option<UsernameNavValue>[]; const usernameOptions = this.toOptions(usernames);
usernameOptions.push({ value: FORWARDER, label: this.i18nService.t("forwardedEmail") }); usernameOptions.push({ value: FORWARDER, label: this.i18nService.t("forwardedEmail") });
const forwarders = algorithms.filter((a) => isForwarderIntegration(a.id)); const forwarders = algorithms.filter((a) => isForwarderIntegration(a.id));
const forwarderOptions = this.toOptions(forwarders) as Option<ForwarderNavValue>[]; const forwarderOptions = this.toOptions(forwarders);
forwarderOptions.unshift({ value: NONE_SELECTED, label: this.i18nService.t("select") }); forwarderOptions.unshift({ value: NONE_SELECTED, label: this.i18nService.t("select") });
return [usernameOptions, forwarderOptions] as const; return [usernameOptions, forwarderOptions] as const;
@ -132,7 +123,7 @@ export class CredentialGeneratorComponent implements OnInit, OnDestroy {
.algorithms$("password", { userId$: this.userId$ }) .algorithms$("password", { userId$: this.userId$ })
.pipe( .pipe(
map((algorithms) => { map((algorithms) => {
const options = this.toOptions(algorithms) as Option<RootNavValue>[]; const options = this.toOptions(algorithms);
options.push({ value: IDENTIFIER, label: this.i18nService.t("username") }); options.push({ value: IDENTIFIER, label: this.i18nService.t("username") });
return options; return options;
}), }),
@ -182,7 +173,7 @@ export class CredentialGeneratorComponent implements OnInit, OnDestroy {
}); });
}); });
const username$ = new Subject<{ nav: UsernameNavValue }>(); const username$ = new Subject<{ nav: string }>();
this.root$ this.root$
.pipe( .pipe(
filter(({ nav }) => !!nav), filter(({ nav }) => !!nav),
@ -190,7 +181,7 @@ export class CredentialGeneratorComponent implements OnInit, OnDestroy {
if (maybeAlgorithm.nav === IDENTIFIER) { if (maybeAlgorithm.nav === IDENTIFIER) {
return concat(of(this.username.value), this.username.valueChanges); return concat(of(this.username.value), this.username.valueChanges);
} else { } else {
return of(maybeAlgorithm as { nav: CredentialAlgorithm }); return of(maybeAlgorithm);
} }
}), }),
takeUntil(this.destroyed), takeUntil(this.destroyed),
@ -205,27 +196,28 @@ export class CredentialGeneratorComponent implements OnInit, OnDestroy {
if (maybeAlgorithm.nav === FORWARDER) { if (maybeAlgorithm.nav === FORWARDER) {
return concat(of(this.forwarder.value), this.forwarder.valueChanges); return concat(of(this.forwarder.value), this.forwarder.valueChanges);
} else { } else {
return of(maybeAlgorithm as { nav: CredentialAlgorithm }); return of(maybeAlgorithm);
} }
}), }),
map((maybeAlgorithm) => { map((maybeAlgorithm) => {
if (maybeAlgorithm.nav === NONE_SELECTED) { if (maybeAlgorithm.nav === NONE_SELECTED) {
return { nav: null }; return { nav: null };
} else { } else {
return maybeAlgorithm as { nav: CredentialAlgorithm }; return maybeAlgorithm;
} }
}), }),
filter(({ nav }) => !!nav), filter(({ nav }) => !!nav),
withLatestFrom(preferences), withLatestFrom(preferences),
takeUntil(this.destroyed), takeUntil(this.destroyed),
) )
.subscribe(([{ nav: algorithm }, preference]) => { .subscribe(([{ nav }, preference]) => {
function setPreference(category: CredentialCategory) { function setPreference(category: CredentialCategory) {
const p = preference[category]; const p = preference[category];
p.algorithm = algorithm; p.algorithm = algorithm;
p.updated = new Date(); p.updated = new Date();
} }
const algorithm = JSON.parse(nav);
// `is*Algorithm` decides `algorithm`'s type, which flows into `setPreference` // `is*Algorithm` decides `algorithm`'s type, which flows into `setPreference`
if (isForwarderIntegration(algorithm) && algorithm.forwarder === null) { if (isForwarderIntegration(algorithm) && algorithm.forwarder === null) {
return; return;
@ -261,19 +253,22 @@ export class CredentialGeneratorComponent implements OnInit, OnDestroy {
// populate the form with the user's preferences to kick off interactivity // populate the form with the user's preferences to kick off interactivity
preferences.pipe(takeUntil(this.destroyed)).subscribe(({ email, username, password }) => { preferences.pipe(takeUntil(this.destroyed)).subscribe(({ email, username, password }) => {
// the last preference set by the user "wins" // the last preference set by the user "wins"
const forwarderPref = isForwarderIntegration(email.algorithm) ? email : null; let forwarderPref = null;
let forwarderId: IntegrationId = null;
if (isForwarderIntegration(email.algorithm)) {
forwarderPref = email;
forwarderId = email.algorithm.forwarder;
}
const usernamePref = email.updated > username.updated ? email : username; const usernamePref = email.updated > username.updated ? email : username;
const rootPref = usernamePref.updated > password.updated ? usernamePref : password; const rootPref = usernamePref.updated > password.updated ? usernamePref : password;
// inject drilldown flags // inject drilldown flags
const forwarderNav = !forwarderPref const forwarderNav = !forwarderPref ? NONE_SELECTED : JSON.stringify(forwarderPref.algorithm);
? NONE_SELECTED const userNav = forwarderPref ? FORWARDER : JSON.stringify(usernamePref.algorithm);
: (forwarderPref.algorithm as ForwarderIntegration);
const userNav = forwarderPref ? FORWARDER : (usernamePref.algorithm as UsernameAlgorithm);
const rootNav = const rootNav =
rootPref.algorithm == usernamePref.algorithm rootPref.algorithm == usernamePref.algorithm
? IDENTIFIER ? IDENTIFIER
: (rootPref.algorithm as PasswordAlgorithm); : JSON.stringify(rootPref.algorithm);
// update navigation; break subscription loop // update navigation; break subscription loop
this.onRootChanged({ nav: rootNav }); this.onRootChanged({ nav: rootNav });
@ -292,7 +287,7 @@ export class CredentialGeneratorComponent implements OnInit, OnDestroy {
this.showForwarder$.next(showForwarder); this.showForwarder$.next(showForwarder);
if (showForwarder && forwarderNav !== NONE_SELECTED) { if (showForwarder && forwarderNav !== NONE_SELECTED) {
this.forwarderId$.next(forwarderNav.forwarder); this.forwarderId$.next(forwarderId);
} else { } else {
this.forwarderId$.next(null); this.forwarderId$.next(null);
} }
@ -342,14 +337,19 @@ export class CredentialGeneratorComponent implements OnInit, OnDestroy {
throw new Error(`Invalid generator type: "${type}"`); throw new Error(`Invalid generator type: "${type}"`);
} }
/** Lists the top-level credential types supported by the component. */ /** Lists the top-level credential types supported by the component.
protected rootOptions$ = new BehaviorSubject<Option<RootNavValue>[]>([]); * @remarks This is string-typed because angular doesn't support
* structural equality for objects, which prevents `CredentialAlgorithm`
* from being selectable within a dropdown when its value contains a
* `ForwarderIntegration`.
*/
protected rootOptions$ = new BehaviorSubject<Option<string>[]>([]);
/** Lists the credential types of the username algorithm box. */ /** Lists the credential types of the username algorithm box. */
protected usernameOptions$ = new BehaviorSubject<Option<UsernameNavValue>[]>([]); protected usernameOptions$ = new BehaviorSubject<Option<string>[]>([]);
/** Lists the credential types of the username algorithm box. */ /** Lists the credential types of the username algorithm box. */
protected forwarderOptions$ = new BehaviorSubject<Option<ForwarderNavValue>[]>([]); protected forwarderOptions$ = new BehaviorSubject<Option<string>[]>([]);
/** Tracks the currently selected forwarder. */ /** Tracks the currently selected forwarder. */
protected forwarderId$ = new BehaviorSubject<IntegrationId>(null); protected forwarderId$ = new BehaviorSubject<IntegrationId>(null);
@ -381,8 +381,8 @@ export class CredentialGeneratorComponent implements OnInit, OnDestroy {
protected readonly generate$ = new Subject<void>(); protected readonly generate$ = new Subject<void>();
private toOptions(algorithms: AlgorithmInfo[]) { private toOptions(algorithms: AlgorithmInfo[]) {
const options: Option<CredentialAlgorithm>[] = algorithms.map((algorithm) => ({ const options: Option<string>[] = algorithms.map((algorithm) => ({
value: algorithm.id, value: JSON.stringify(algorithm.id),
label: algorithm.name, label: algorithm.name,
})); }));