mirror of
https://github.com/bitwarden/browser
synced 2026-02-08 20:50:28 +00:00
fix engine-settings desync error
This commit is contained in:
13
libs/common/src/tools/rx.rxjs.ts
Normal file
13
libs/common/src/tools/rx.rxjs.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
/**
|
||||
* Used to infer types from arguments to functions like {@link withLatestReady}.
|
||||
* So that you can have `forkJoin([Observable<A>, PromiseLike<B>]): Observable<[A, B]>`
|
||||
* et al.
|
||||
* @remarks this type definition is derived from rxjs' {@link ObservableInputTuple}.
|
||||
* The difference is it *only* works with observables, while the rx version works
|
||||
* with any thing that can become an observable.
|
||||
*/
|
||||
export type ObservableTuple<T> = {
|
||||
[K in keyof T]: Observable<T[K]>;
|
||||
};
|
||||
@@ -20,8 +20,11 @@ import {
|
||||
startWith,
|
||||
pairwise,
|
||||
MonoTypeOperatorFunction,
|
||||
Cons,
|
||||
} from "rxjs";
|
||||
|
||||
import { ObservableTuple } from "./rx.rxjs";
|
||||
|
||||
/** Returns its input. */
|
||||
function identity(value: any): any {
|
||||
return value;
|
||||
@@ -164,26 +167,30 @@ export function ready<T>(watch$: Observable<any> | Observable<any>[]) {
|
||||
);
|
||||
}
|
||||
|
||||
export function withLatestReady<Source, Watch>(
|
||||
watch$: Observable<Watch>,
|
||||
): OperatorFunction<Source, [Source, Watch]> {
|
||||
export function withLatestReady<Source, Watch extends readonly unknown[]>(
|
||||
...watches$: [...ObservableTuple<Watch>]
|
||||
): OperatorFunction<Source, Cons<Source, Watch>> {
|
||||
return connect((source$) => {
|
||||
// these subscriptions are safe because `source$` connects only after there
|
||||
// is an external subscriber.
|
||||
const source = new ReplaySubject<Source>(1);
|
||||
source$.subscribe(source);
|
||||
const watch = new ReplaySubject<Watch>(1);
|
||||
watch$.subscribe(watch);
|
||||
|
||||
const watches = watches$.map((w) => {
|
||||
const watch$ = new ReplaySubject<unknown>(1);
|
||||
w.subscribe(watch$);
|
||||
return watch$;
|
||||
}) as [...ObservableTuple<Watch>];
|
||||
|
||||
// `concat` is subscribed immediately after it's returned, at which point
|
||||
// `zip` blocks until all items in `watching$` are ready. If that occurs
|
||||
// `zip` blocks until all items in `watches` are ready. If that occurs
|
||||
// after `source$` is hot, then the replay subject sends the last-captured
|
||||
// emission through immediately. Otherwise, `ready` waits for the next
|
||||
// emission
|
||||
return concat(zip(watch).pipe(first(), ignoreElements()), source).pipe(
|
||||
withLatestFrom(watch),
|
||||
// emission through immediately. Otherwise, `withLatestFrom` waits for the
|
||||
// next emission
|
||||
return concat(zip(watches).pipe(first(), ignoreElements()), source).pipe(
|
||||
withLatestFrom(...watches),
|
||||
takeUntil(anyComplete(source)),
|
||||
);
|
||||
) as Observable<Cons<Source, Watch>>;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -40,13 +40,13 @@
|
||||
</bit-card>
|
||||
<tools-password-settings
|
||||
class="tw-mt-6"
|
||||
*ngIf="(showAlgorithm$ | async)?.id === 'password'"
|
||||
*ngIf="(showAlgorithm$ | async)?.id === Algorithm.password"
|
||||
[account]="account$ | async"
|
||||
(onUpdated)="generate('password settings')"
|
||||
/>
|
||||
<tools-passphrase-settings
|
||||
class="tw-mt-6"
|
||||
*ngIf="(showAlgorithm$ | async)?.id === 'passphrase'"
|
||||
*ngIf="(showAlgorithm$ | async)?.id === Algorithm.passphrase"
|
||||
[account]="account$ | async"
|
||||
(onUpdated)="generate('passphrase settings')"
|
||||
/>
|
||||
@@ -82,7 +82,7 @@
|
||||
</bit-form-field>
|
||||
</form>
|
||||
<tools-catchall-settings
|
||||
*ngIf="(showAlgorithm$ | async)?.id === 'catchall'"
|
||||
*ngIf="(showAlgorithm$ | async)?.id === Algorithm.catchall"
|
||||
[account]="account$ | async"
|
||||
(onUpdated)="generate('catchall settings')"
|
||||
/>
|
||||
@@ -92,12 +92,12 @@
|
||||
[forwarder]="forwarderId$ | async"
|
||||
/>
|
||||
<tools-subaddress-settings
|
||||
*ngIf="(showAlgorithm$ | async)?.id === 'subaddress'"
|
||||
*ngIf="(showAlgorithm$ | async)?.id === Algorithm.plusAddress"
|
||||
[account]="account$ | async"
|
||||
(onUpdated)="generate('subaddress settings')"
|
||||
/>
|
||||
<tools-username-settings
|
||||
*ngIf="(showAlgorithm$ | async)?.id === 'username'"
|
||||
*ngIf="(showAlgorithm$ | async)?.id === Algorithm.username"
|
||||
[account]="account$ | async"
|
||||
(onUpdated)="generate('username settings')"
|
||||
/>
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
ReplaySubject,
|
||||
Subject,
|
||||
takeUntil,
|
||||
tap,
|
||||
withLatestFrom,
|
||||
} from "rxjs";
|
||||
|
||||
@@ -49,6 +50,7 @@ import {
|
||||
isPasswordAlgorithm,
|
||||
CredentialAlgorithm,
|
||||
AlgorithmMetadata,
|
||||
Algorithm,
|
||||
} from "@bitwarden/generator-core";
|
||||
import { GeneratorHistoryService } from "@bitwarden/generator-history";
|
||||
|
||||
@@ -79,6 +81,8 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro
|
||||
private ariaLive: LiveAnnouncer,
|
||||
) {}
|
||||
|
||||
protected readonly Algorithm = Algorithm;
|
||||
|
||||
/** Binds the component to a specific user's settings. When this input is not provided,
|
||||
* the form binds to the active user
|
||||
*/
|
||||
@@ -91,7 +95,7 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro
|
||||
* @warning this may reveal sensitive information in plaintext.
|
||||
*/
|
||||
@Input()
|
||||
debug: boolean = false;
|
||||
debug: boolean = true;
|
||||
|
||||
// this `log` initializer is overridden in `ngOnInit`
|
||||
private log: SemanticLogger = disabledSemanticLoggerProvider({});
|
||||
@@ -230,7 +234,9 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro
|
||||
// wire up the generator
|
||||
this.generatorService
|
||||
.generator$({
|
||||
on$: this.generate$,
|
||||
on$: this.generate$.pipe(
|
||||
tap((g) => this.log.debug(g, "generate request issued by component")),
|
||||
),
|
||||
account$: this.account$,
|
||||
})
|
||||
.pipe(
|
||||
@@ -290,7 +296,7 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro
|
||||
} else if (root.nav) {
|
||||
return { nav: root.nav, algorithm: JSON.parse(root.nav) };
|
||||
} else {
|
||||
this.log.panic(root, "unknown navigation value.");
|
||||
return { nav: IDENTIFIER };
|
||||
}
|
||||
}),
|
||||
takeUntil(this.destroyed),
|
||||
@@ -305,7 +311,7 @@ export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestro
|
||||
} else if (username.nav) {
|
||||
return { nav: username.nav, algorithm: JSON.parse(username.nav) };
|
||||
} else {
|
||||
this.log.panic(username, "unknown navigation value.");
|
||||
return { nav: FORWARDER };
|
||||
}
|
||||
}),
|
||||
takeUntil(this.destroyed),
|
||||
|
||||
@@ -32,7 +32,7 @@ type MockTwoLevelPartial<T> = {
|
||||
: T[K];
|
||||
};
|
||||
|
||||
describe("CredentialGeneratorService", () => {
|
||||
describe("DefaultCredentialGeneratorService", () => {
|
||||
let service: DefaultCredentialGeneratorService;
|
||||
let providers: MockTwoLevelPartial<CredentialGeneratorProviders>;
|
||||
let system: any;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
EMPTY,
|
||||
concatMap,
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
@@ -60,12 +61,14 @@ export class DefaultCredentialGeneratorService implements CredentialGeneratorSer
|
||||
} else if (isTypeRequest(requested)) {
|
||||
return this.provide.metadata.preference$(requested.type, { account$ });
|
||||
} else {
|
||||
this.log.panic(requested, "algorithm or category required");
|
||||
this.log.warn(requested, "algorithm or category required");
|
||||
return EMPTY;
|
||||
}
|
||||
}),
|
||||
filter((algorithm): algorithm is CredentialAlgorithm => !!algorithm),
|
||||
distinctUntilChanged(),
|
||||
map((algorithm) => this.provide.metadata.metadata(algorithm)),
|
||||
distinctUntilChanged((previous, current) => previous.id === current.id),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
|
||||
// load the active profile's algorithm settings
|
||||
@@ -84,9 +87,8 @@ export class DefaultCredentialGeneratorService implements CredentialGeneratorSer
|
||||
|
||||
// generation proper
|
||||
const generate$ = on$.pipe(
|
||||
withLatestReady(engine$),
|
||||
withLatestReady(settings$),
|
||||
concatMap(([[request, engine], settings]) => engine.generate(request, settings)),
|
||||
withLatestReady(settings$, engine$),
|
||||
concatMap(([request, settings, engine]) => engine.generate(request, settings)),
|
||||
takeUntil(anyComplete([settings$])),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user