diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index 9b1d8096fc7..e6c22961673 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -923,4 +923,75 @@ describe("Cipher Service", () => { sub.unsubscribe(); }); }); + + describe("getCipherForUrl localData application", () => { + beforeEach(() => { + Object.defineProperty(autofillSettingsService, "autofillOnPageLoadDefault$", { + value: of(true), + writable: true, + }); + }); + + it("should apply localData to ciphers when getCipherForUrl is called via getLastLaunchedForUrl", async () => { + const testUrl = "https://test-url.com"; + const cipherId = "test-cipher-id" as CipherId; + const testLocalData = { + lastLaunched: Date.now().valueOf(), + lastUsedDate: Date.now().valueOf() - 1000, + }; + + jest.spyOn(cipherService, "localData$").mockReturnValue(of({ [cipherId]: testLocalData })); + + const mockCipherView = new CipherView(); + mockCipherView.id = cipherId; + mockCipherView.localData = null; + + jest.spyOn(cipherService, "getAllDecryptedForUrl").mockResolvedValue([mockCipherView]); + + const result = await cipherService.getLastLaunchedForUrl(testUrl, userId, true); + + expect(result.localData).toEqual(testLocalData); + }); + + it("should apply localData to ciphers when getCipherForUrl is called via getLastUsedForUrl", async () => { + const testUrl = "https://test-url.com"; + const cipherId = "test-cipher-id" as CipherId; + const testLocalData = { lastUsedDate: Date.now().valueOf() - 1000 }; + + jest.spyOn(cipherService, "localData$").mockReturnValue(of({ [cipherId]: testLocalData })); + + const mockCipherView = new CipherView(); + mockCipherView.id = cipherId; + mockCipherView.localData = null; + + jest.spyOn(cipherService, "getAllDecryptedForUrl").mockResolvedValue([mockCipherView]); + + const result = await cipherService.getLastUsedForUrl(testUrl, userId, true); + + expect(result.localData).toEqual(testLocalData); + }); + + it("should not modify localData if it already matches in getCipherForUrl", async () => { + const testUrl = "https://test-url.com"; + const cipherId = "test-cipher-id" as CipherId; + const existingLocalData = { + lastLaunched: Date.now().valueOf(), + lastUsedDate: Date.now().valueOf() - 1000, + }; + + jest + .spyOn(cipherService, "localData$") + .mockReturnValue(of({ [cipherId]: existingLocalData })); + + const mockCipherView = new CipherView(); + mockCipherView.id = cipherId; + mockCipherView.localData = existingLocalData; + + jest.spyOn(cipherService, "getAllDecryptedForUrl").mockResolvedValue([mockCipherView]); + + const result = await cipherService.getLastLaunchedForUrl(testUrl, userId, true); + + expect(result.localData).toBe(existingLocalData); + }); + }); }); diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 4bdc0d9b9fd..41f94e02cdf 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -1947,13 +1947,23 @@ export class CipherService implements CipherServiceAbstraction { autofillOnPageLoad: boolean, ): Promise { const cacheKey = autofillOnPageLoad ? "autofillOnPageLoad-" + url : url; - if (!this.sortedCiphersCache.isCached(cacheKey)) { let ciphers = await this.getAllDecryptedForUrl(url, userId); - if (!ciphers) { + + if (!ciphers?.length) { return null; } + const localData = await firstValueFrom(this.localData$(userId)); + if (localData) { + for (const view of ciphers) { + const data = localData[view.id as CipherId]; + if (data) { + view.localData = data; + } + } + } + if (autofillOnPageLoad) { const autofillOnPageLoadDefault = await this.getAutofillOnPageLoadDefault();