1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 15:53:27 +00:00

[PM-11249] Sync attachment updates across platforms (#11758)

* update extension refresh form when an attachment is added or removed

- This is needed because the revision date was updated on the server and the locally stored cipher needs to match.

* receive updated cipher from delete attachment endpoint

- deleting an attachment will now alter the revision timestamp on a cipher.

* patch the cipher when an attachment is added or deleted

* migrate vault component to use the `cipherViews$` observable

* reference `cipherViews$` on desktop for vault-items

- This avoid race conditions where ciphers are cleared out in the background. `cipherViews` should always emit the latest views

* return CipherData from cipher service so that consumers have the updated cipher right away

* use the updated cipher from attachment endpoints to refresh the details within the add/edit components on desktop
This commit is contained in:
Nick Krantz
2025-01-28 10:01:23 -06:00
committed by GitHub
parent 70ea75d8f7
commit 7c2bf504a3
10 changed files with 95 additions and 26 deletions

View File

@@ -702,7 +702,7 @@ export class ApiService implements ApiServiceAbstraction {
}
deleteCipherAttachment(id: string, attachmentId: string): Promise<any> {
return this.send("DELETE", "/ciphers/" + id + "/attachment/" + attachmentId, null, true, false);
return this.send("DELETE", "/ciphers/" + id + "/attachment/" + attachmentId, null, true, true);
}
deleteCipherAttachmentAdmin(id: string, attachmentId: string): Promise<any> {

View File

@@ -154,8 +154,8 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe
delete: (id: string | string[]) => Promise<any>;
deleteWithServer: (id: string, asAdmin?: boolean) => Promise<any>;
deleteManyWithServer: (ids: string[], asAdmin?: boolean) => Promise<any>;
deleteAttachment: (id: string, attachmentId: string) => Promise<void>;
deleteAttachmentWithServer: (id: string, attachmentId: string) => Promise<void>;
deleteAttachment: (id: string, revisionDate: string, attachmentId: string) => Promise<CipherData>;
deleteAttachmentWithServer: (id: string, attachmentId: string) => Promise<CipherData>;
sortCiphersByLastUsed: (a: CipherView, b: CipherView) => number;
sortCiphersByLastUsedThenName: (a: CipherView, b: CipherView) => number;
getLocaleSortingFunction: () => (a: CipherView, b: CipherView) => number;

View File

@@ -1078,7 +1078,11 @@ export class CipherService implements CipherServiceAbstraction {
await this.delete(ids);
}
async deleteAttachment(id: string, attachmentId: string): Promise<void> {
async deleteAttachment(
id: string,
revisionDate: string,
attachmentId: string,
): Promise<CipherData> {
let ciphers = await firstValueFrom(this.ciphers$);
const cipherId = id as CipherId;
// eslint-disable-next-line
@@ -1092,6 +1096,10 @@ export class CipherService implements CipherServiceAbstraction {
}
}
// Deleting the cipher updates the revision date on the server,
// Update the stored `revisionDate` to match
ciphers[cipherId].revisionDate = revisionDate;
await this.clearCache();
await this.encryptedCiphersState.update(() => {
if (ciphers == null) {
@@ -1099,15 +1107,20 @@ export class CipherService implements CipherServiceAbstraction {
}
return ciphers;
});
return ciphers[cipherId];
}
async deleteAttachmentWithServer(id: string, attachmentId: string): Promise<void> {
async deleteAttachmentWithServer(id: string, attachmentId: string): Promise<CipherData> {
let cipherResponse = null;
try {
await this.apiService.deleteCipherAttachment(id, attachmentId);
cipherResponse = await this.apiService.deleteCipherAttachment(id, attachmentId);
} catch (e) {
return Promise.reject((e as ErrorResponse).getSingleMessage());
}
await this.deleteAttachment(id, attachmentId);
const cipherData = CipherData.fromJSON(cipherResponse?.cipher);
return await this.deleteAttachment(id, cipherData.revisionDate, attachmentId);
}
sortCiphersByLastUsed(a: CipherView, b: CipherView): number {