1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-22 04:13:49 +00:00

[PM-29972] Update Vault Items List When Archiving Ciphers (#18102)

* update default cipher service to use upsert, apply optional userId parameter
This commit is contained in:
Jason Ng
2025-12-29 13:49:00 -05:00
committed by GitHub
parent 1c16b8edb9
commit 3beeab4414
4 changed files with 22 additions and 15 deletions

View File

@@ -207,9 +207,13 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe
* Update the local store of CipherData with the provided data. Values are upserted into the existing store.
*
* @param cipher The cipher data to upsert. Can be a single CipherData object or an array of CipherData objects.
* @param userId Optional user ID for whom the cipher data is being upserted.
* @returns A promise that resolves to a record of updated cipher store, keyed by their cipher ID. Returns all ciphers, not just those updated
*/
abstract upsert(cipher: CipherData | CipherData[]): Promise<Record<CipherId, CipherData>>;
abstract upsert(
cipher: CipherData | CipherData[],
userId?: UserId,
): Promise<Record<CipherId, CipherData>>;
abstract replace(ciphers: { [id: string]: CipherData }, userId: UserId): Promise<any>;
abstract clear(userId?: string): Promise<void>;
abstract moveManyWithServer(ids: string[], folderId: string, userId: UserId): Promise<any>;

View File

@@ -1196,12 +1196,15 @@ export class CipherService implements CipherServiceAbstraction {
await this.encryptedCiphersState(userId).update(() => ciphers);
}
async upsert(cipher: CipherData | CipherData[]): Promise<Record<CipherId, CipherData>> {
async upsert(
cipher: CipherData | CipherData[],
userId?: UserId,
): Promise<Record<CipherId, CipherData>> {
const ciphers = cipher instanceof CipherData ? [cipher] : cipher;
const res = await this.updateEncryptedCipherState((current) => {
ciphers.forEach((c) => (current[c.id as CipherId] = c));
return current;
});
}, userId);
// Some state storage providers (e.g. Electron) don't update the state immediately, wait for next tick
// Otherwise, subscribers to cipherViews$ can get stale data
await new Promise((resolve) => setTimeout(resolve, 0));

View File

@@ -219,7 +219,7 @@ describe("DefaultCipherArchiveService", () => {
} as any,
}),
);
mockCipherService.replace.mockResolvedValue(undefined);
mockCipherService.upsert.mockResolvedValue(undefined);
});
it("should archive single cipher", async () => {
@@ -233,13 +233,13 @@ describe("DefaultCipherArchiveService", () => {
true,
);
expect(mockCipherService.ciphers$).toHaveBeenCalledWith(userId);
expect(mockCipherService.replace).toHaveBeenCalledWith(
expect.objectContaining({
[cipherId]: expect.objectContaining({
expect(mockCipherService.upsert).toHaveBeenCalledWith(
[
expect.objectContaining({
archivedDate: "2024-01-15T10:30:00.000Z",
revisionDate: "2024-01-15T10:31:00.000Z",
}),
}),
],
userId,
);
});
@@ -282,7 +282,7 @@ describe("DefaultCipherArchiveService", () => {
} as any,
}),
);
mockCipherService.replace.mockResolvedValue(undefined);
mockCipherService.upsert.mockResolvedValue(undefined);
});
it("should unarchive single cipher", async () => {
@@ -296,12 +296,12 @@ describe("DefaultCipherArchiveService", () => {
true,
);
expect(mockCipherService.ciphers$).toHaveBeenCalledWith(userId);
expect(mockCipherService.replace).toHaveBeenCalledWith(
expect.objectContaining({
[cipherId]: expect.objectContaining({
expect(mockCipherService.upsert).toHaveBeenCalledWith(
[
expect.objectContaining({
revisionDate: "2024-01-15T10:31:00.000Z",
}),
}),
],
userId,
);
});

View File

@@ -95,7 +95,7 @@ export class DefaultCipherArchiveService implements CipherArchiveService {
localCipher.revisionDate = cipher.revisionDate;
}
await this.cipherService.replace(currentCiphers, userId);
await this.cipherService.upsert(Object.values(currentCiphers), userId);
}
async unarchiveWithServer(ids: CipherId | CipherId[], userId: UserId): Promise<void> {
@@ -116,6 +116,6 @@ export class DefaultCipherArchiveService implements CipherArchiveService {
localCipher.revisionDate = cipher.revisionDate;
}
await this.cipherService.replace(currentCiphers, userId);
await this.cipherService.upsert(Object.values(currentCiphers), userId);
}
}