1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 00:03:56 +00:00

Pm-10953/add-user-context-to-sync-replaces (#10627)

* Require userId for setting masterKeyEncryptedUserKey

* Replace folders for specified user

* Require userId for collection replace

* Cipher Replace requires userId

* Require UserId to update equivalent domains

* Require userId for policy replace

* sync state updates between fake state for better testing

* Revert to public observable tests

Since they now sync, we can test single-user updates impacting active user observables

* Do not init fake states through sync

Do not sync initial null values, that might wipe out already existing data.

* Require userId for Send replace

* Include userId for organization replace

* Require userId for billing sync data

* Require user Id for key connector sync data

* Allow decode of token by userId

* Require userId for synced key connector updates

* Add userId to policy setting during organization invite accept

* Fix cli

* Handle null userId

---------

Co-authored-by: bnagawiecki <107435978+bnagawiecki@users.noreply.github.com>
This commit is contained in:
Matt Gibson
2024-08-26 17:44:08 -07:00
committed by GitHub
parent 866a624e44
commit 9459cda304
46 changed files with 666 additions and 484 deletions

View File

@@ -1,4 +1,4 @@
import { Observable, ReplaySubject, concatMap, firstValueFrom, map, timeout } from "rxjs";
import { Observable, ReplaySubject, concatMap, filter, firstValueFrom, map, timeout } from "rxjs";
import {
DerivedState,
@@ -41,6 +41,10 @@ export class FakeGlobalState<T> implements GlobalState<T> {
this.stateSubject.next(initialValue ?? null);
}
nextState(state: T) {
this.stateSubject.next(state);
}
async update<TCombine>(
configureState: (state: T, dependency: TCombine) => T,
options?: StateUpdateOptions<T, TCombine>,
@@ -89,7 +93,10 @@ export class FakeGlobalState<T> implements GlobalState<T> {
export class FakeSingleUserState<T> implements SingleUserState<T> {
// eslint-disable-next-line rxjs/no-exposed-subjects -- exposed for testing setup
stateSubject = new ReplaySubject<CombinedState<T>>(1);
stateSubject = new ReplaySubject<{
syncValue: boolean;
combinedState: CombinedState<T>;
}>(1);
state$: Observable<T>;
combinedState$: Observable<CombinedState<T>>;
@@ -97,15 +104,28 @@ export class FakeSingleUserState<T> implements SingleUserState<T> {
constructor(
readonly userId: UserId,
initialValue?: T,
updateSyncCallback?: (userId: UserId, newValue: T) => Promise<void>,
) {
this.stateSubject.next([userId, initialValue ?? null]);
// Inform the state provider of updates to keep active user states in sync
this.stateSubject
.pipe(
filter((next) => next.syncValue),
concatMap(async ({ combinedState }) => {
await updateSyncCallback?.(...combinedState);
}),
)
.subscribe();
this.nextState(initialValue ?? null, { syncValue: initialValue != null });
this.combinedState$ = this.stateSubject.asObservable();
this.combinedState$ = this.stateSubject.pipe(map((v) => v.combinedState));
this.state$ = this.combinedState$.pipe(map(([_userId, state]) => state));
}
nextState(state: T) {
this.stateSubject.next([this.userId, state]);
nextState(state: T, { syncValue }: { syncValue: boolean } = { syncValue: true }) {
this.stateSubject.next({
syncValue,
combinedState: [this.userId, state],
});
}
async update<TCombine>(
@@ -122,7 +142,7 @@ export class FakeSingleUserState<T> implements SingleUserState<T> {
return current;
}
const newState = configureState(current, combinedDependencies);
this.stateSubject.next([this.userId, newState]);
this.nextState(newState);
this.nextMock(newState);
return newState;
}
@@ -146,7 +166,10 @@ export class FakeActiveUserState<T> implements ActiveUserState<T> {
[activeMarker]: true;
// eslint-disable-next-line rxjs/no-exposed-subjects -- exposed for testing setup
stateSubject = new ReplaySubject<CombinedState<T>>(1);
stateSubject = new ReplaySubject<{
syncValue: boolean;
combinedState: CombinedState<T>;
}>(1);
state$: Observable<T>;
combinedState$: Observable<CombinedState<T>>;
@@ -154,10 +177,18 @@ export class FakeActiveUserState<T> implements ActiveUserState<T> {
constructor(
private accountService: FakeAccountService,
initialValue?: T,
updateSyncCallback?: (userId: UserId, newValue: T) => Promise<void>,
) {
this.stateSubject.next([accountService.activeUserId, initialValue ?? null]);
// Inform the state provider of updates to keep single user states in sync
this.stateSubject.pipe(
filter((next) => next.syncValue),
concatMap(async ({ combinedState }) => {
await updateSyncCallback?.(...combinedState);
}),
);
this.nextState(initialValue ?? null, { syncValue: initialValue != null });
this.combinedState$ = this.stateSubject.asObservable();
this.combinedState$ = this.stateSubject.pipe(map((v) => v.combinedState));
this.state$ = this.combinedState$.pipe(map(([_userId, state]) => state));
}
@@ -165,8 +196,11 @@ export class FakeActiveUserState<T> implements ActiveUserState<T> {
return this.accountService.activeUserId;
}
nextState(state: T) {
this.stateSubject.next([this.userId, state]);
nextState(state: T, { syncValue }: { syncValue: boolean } = { syncValue: true }) {
this.stateSubject.next({
syncValue,
combinedState: [this.userId, state],
});
}
async update<TCombine>(
@@ -183,7 +217,7 @@ export class FakeActiveUserState<T> implements ActiveUserState<T> {
return [this.userId, current];
}
const newState = configureState(current, combinedDependencies);
this.stateSubject.next([this.userId, newState]);
this.nextState(newState);
this.nextMock([this.userId, newState]);
return [this.userId, newState];
}