1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 21:33:27 +00:00

[PM-18903] Desktop sync issues (#13681)

* [PM-18707] Use different BroadcasterSubscriptionId in base view component to avoid collision with desktop view component

* [PM-18707] Use userId instead of payloadUserId for cipher notification syncs

* [PM-19032] Live Sync on Desktop (#13851)

* migrate the vault-items to an observables rather than async/promises

- this helps keep data in sync with the service state and avoids race conditions

* migrate the view component to an observables rather than async/promises

- this helps keep data in sync with the service state and avoids race conditions

* decrypt saved cipher from server

* bump timeout for upserting ciphers

* mark `go` as async in desktop vault

- previously it was a floating promise

* Revert "mark `go` as async in desktop vault"

This reverts commit fd28f40b18.

* Revert "bump timeout for upserting ciphers"

This reverts commit e963acc377.

* move vault utilities to `common` rather than `lib` to avoid circular dependencies

* use `perUserCache$` for `cipherViews$` to avoid new subscriptions from being created

* use userId from observable rather than locally set to be the most up to date

* [PM-18707] Add clearBuffer$ input to perUserCache$ helper so that  the internal share replay buffers can be cleared

* [PM-18707] Rework forceCipherViews$ to clearBuffer$ refactor

- Add dependency for cipherDecryptionKeys$ for the cipherViews so that decryption is never attempted without keys

* [PM-18707] Add overload to perUserCache to satisfy type checker

* [PM-18707] Fix overloads

* [PM-18707] Add check for empty failed to decrypt ciphers

* [PM-18707] Mark vault component for check after observable emits.

The cipherViews$ observable now persists between subscriptions, meaning that updates via the sync push notifications can occur outside the AngularZone causing delays in updating the view.

---------

Co-authored-by: Nick Krantz <125900171+nick-livefront@users.noreply.github.com>
Co-authored-by: Nick Krantz <nick@livefront.com>
This commit is contained in:
Shane Melton
2025-04-15 12:17:41 -07:00
committed by GitHub
parent 4cddc40828
commit 8258ea39b0
10 changed files with 239 additions and 168 deletions

View File

@@ -27,9 +27,6 @@ export class VaultItemsComponent extends BaseVaultItemsComponent {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
searchBarService.searchText$.pipe(distinctUntilChanged()).subscribe((searchText) => {
this.searchText = searchText;
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.search(200);
});
}

View File

@@ -126,9 +126,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro
}
async ngOnChanges() {
await super.load();
if (this.cipher.decryptionFailure) {
if (this.cipher?.decryptionFailure) {
DecryptionFailureDialogComponent.open(this.dialogService, {
cipherIds: [this.cipherId as CipherId],
});

View File

@@ -13,6 +13,7 @@ import {
Subject,
} from "rxjs";
import {
catchError,
concatMap,
debounceTime,
filter,
@@ -23,7 +24,6 @@ import {
take,
takeUntil,
tap,
catchError,
} from "rxjs/operators";
import {
@@ -64,6 +64,7 @@ import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-repromp
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { ServiceUtils } from "@bitwarden/common/vault/service-utils";
import { filterOutNullish } from "@bitwarden/common/vault/utils/observable-utilities";
import { DialogRef, DialogService, Icons, ToastService } from "@bitwarden/components";
import {
AddEditFolderDialogComponent,
@@ -138,7 +139,6 @@ const SearchTextDebounceInterval = 200;
VaultFilterModule,
VaultItemsModule,
SharedModule,
DecryptionFailureDialogComponent,
],
providers: [
RoutedVaultFilterService,
@@ -348,9 +348,8 @@ export class VaultComponent implements OnInit, OnDestroy {
]).pipe(
filter(([ciphers, filter]) => ciphers != undefined && filter != undefined),
concatMap(async ([ciphers, filter, searchText]) => {
const failedCiphers = await firstValueFrom(
this.cipherService.failedToDecryptCiphers$(activeUserId),
);
const failedCiphers =
(await firstValueFrom(this.cipherService.failedToDecryptCiphers$(activeUserId))) ?? [];
const filterFunction = createFilterFunction(filter);
// Append any failed to decrypt ciphers to the top of the cipher list
const allCiphers = [...failedCiphers, ...ciphers];
@@ -472,6 +471,7 @@ export class VaultComponent implements OnInit, OnDestroy {
firstSetup$
.pipe(
switchMap(() => this.cipherService.failedToDecryptCiphers$(activeUserId)),
filterOutNullish(),
map((ciphers) => ciphers.filter((c) => !c.isDeleted)),
filter((ciphers) => ciphers.length > 0),
take(1),
@@ -528,6 +528,10 @@ export class VaultComponent implements OnInit, OnDestroy {
this.isEmpty = collections?.length === 0 && ciphers?.length === 0;
this.performingInitialLoad = false;
this.refreshing = false;
// Explicitly mark for check to ensure the view is updated
// Some sources are not always emitted within the Angular zone (e.g. ciphers updated via WS notifications)
this.changeDetectorRef.markForCheck();
},
);
}