diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index d7aef0db375..9dc2bff65e5 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -145,6 +145,7 @@ export default class RuntimeBackground { if (totpCode != null) { this.platformUtilsService.copyToClipboard(totpCode); } + await this.main.updateOverlayCiphers(); break; } case ExtensionCommand.AutofillCard: { diff --git a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts index f271b255c3e..718043b4e85 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.spec.ts @@ -204,6 +204,7 @@ describe("VaultPopupAutofillService", () => { describe("doAutofill()", () => { it("should return true if autofill is successful", async () => { + mockCipher.id = "test-cipher-id"; mockAutofillService.doAutoFill.mockResolvedValue(null); const result = await service.doAutofill(mockCipher); expect(result).toBe(true); @@ -251,6 +252,7 @@ describe("VaultPopupAutofillService", () => { }); it("should copy TOTP code to clipboard if available", async () => { + mockCipher.id = "test-cipher-id-with-totp"; const totpCode = "123456"; mockAutofillService.doAutoFill.mockResolvedValue(totpCode); await service.doAutofill(mockCipher); @@ -405,5 +407,26 @@ describe("VaultPopupAutofillService", () => { }); }); }); + describe("handleAutofillSuggestionUsed", () => { + const cipherId = "cipher-123"; + + beforeEach(() => { + mockCipherService.updateLastUsedDate.mockResolvedValue(undefined); + }); + + it("updates last used date when there is an active user", async () => { + await service.handleAutofillSuggestionUsed({ cipherId }); + + expect(mockCipherService.updateLastUsedDate).toHaveBeenCalledTimes(1); + expect(mockCipherService.updateLastUsedDate).toHaveBeenCalledWith(cipherId, mockUserId); + }); + + it("does nothing when there is no active user", async () => { + accountService.activeAccount$ = of(null); + await service.handleAutofillSuggestionUsed({ cipherId }); + + expect(mockCipherService.updateLastUsedDate).not.toHaveBeenCalled(); + }); + }); }); }); diff --git a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts index 2d30e857573..3d5b35cded6 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts @@ -16,6 +16,7 @@ import { } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { isUrlInList } from "@bitwarden/common/autofill/utils"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -268,6 +269,7 @@ export class VaultPopupAutofillService { }); return false; } + await this.handleAutofillSuggestionUsed({ cipherId: cipher.id }); return true; } @@ -326,6 +328,21 @@ export class VaultPopupAutofillService { return didAutofill; } + /** + * When a user autofills with an autofill suggestion outside of the inline menu, + * update the cipher's last used date. + * + * @param message - The message containing the cipher ID that was used + */ + async handleAutofillSuggestionUsed(message: { cipherId: string }) { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getOptionalUserId), + ); + if (activeUserId) { + await this.cipherService.updateLastUsedDate(message.cipherId, activeUserId); + } + } + /** * Attempts to autofill the given cipher and, upon successful autofill, saves the URI to the cipher. * Will copy any TOTP code to the clipboard if available after successful autofill.