diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index 66b324e4da4..fc53ca4f296 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -3595,7 +3595,7 @@ describe("OverlayBackground", () => { describe("refreshGeneratedPassword", () => { it("refreshes the generated password", async () => { - overlayBackground["generatedPassword"] = "populated"; + overlayBackground["credential$"].next("populated"); sendPortMessage(listMessageConnectorSpy, { command: "refreshGeneratedPassword", portKey }); await flushPromises(); @@ -3604,9 +3604,9 @@ describe("OverlayBackground", () => { }); it("sends a message to the list port indicating that the generated password should be updated", async () => { - overlayBackground["generatedPassword"] = "refresh"; - sendPortMessage(listMessageConnectorSpy, { command: "refreshGeneratedPassword", portKey }); + overlayBackground["credential$"].next("refresh"); + await flushPromises(); expect(listPortSpy.postMessage).toHaveBeenCalledWith({ @@ -3631,7 +3631,7 @@ describe("OverlayBackground", () => { }, sender, ); - overlayBackground["generatedPassword"] = generatedPassword; + overlayBackground["credential$"].next(generatedPassword); overlayBackground["pageDetailsForTab"][sender.tab.id] = new Map([ [sender.frameId, createPageDetailMock()], ]); @@ -3639,7 +3639,7 @@ describe("OverlayBackground", () => { describe("skipping filling the generated password", () => { it("skips filling when the password has not been created", () => { - overlayBackground["generatedPassword"] = ""; + overlayBackground["credential$"].next(null); sendPortMessage(listMessageConnectorSpy, { command: "fillGeneratedPassword", portKey }); diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index b164e54593f..6870c0b096c 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -1,15 +1,17 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { + BehaviorSubject, + concatMap, debounceTime, firstValueFrom, map, + mapTo, merge, Observable, ReplaySubject, Subject, switchMap, - tap, throttleTime, } from "rxjs"; import { parse } from "tldts"; @@ -126,6 +128,9 @@ export class OverlayBackground implements OverlayBackgroundInterface { private readonly rebuildSubFrameOffsets$ = new Subject(); private readonly addNewVaultItem$ = new Subject(); private readonly requestGeneratedPassword$ = new Subject(); + private readonly clearGeneratedPassword$ = new Subject(); + private yieldedPassword$: Observable; + protected credential$ = new BehaviorSubject(""); private pageDetailsForTab: PageDetailsForTab = {}; private subFrameOffsetsForTab: SubFrameOffsetsForTab = {}; private portKeyForTab: Record = {}; @@ -150,7 +155,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { private showPasskeysLabelsWithinInlineMenu: boolean = false; private iconsServerUrl: string; private generatedPassword: string; - private yieldedPassword$: Observable; + private readonly validPortConnections: Set = new Set([ AutofillOverlayPort.Button, AutofillOverlayPort.ButtonMessageConnector, @@ -242,7 +247,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { private yieldGeneratedPassword: ( $on: Observable, ) => Observable, - private addPasswordCallback: (password: string) => Promise, + private trackCredentialHistory: (password: string) => Promise, ) { this.initOverlayEventObservables(); } @@ -255,15 +260,19 @@ export class OverlayBackground implements OverlayBackgroundInterface { this.setupExtensionListeners(); const env = await firstValueFrom(this.environmentService.environment$); this.iconsServerUrl = env.getIconsUrl(); - this.yieldedPassword$ = this.yieldGeneratedPassword(this.requestGeneratedPassword$); + this.yieldedPassword$ = merge( + this.yieldGeneratedPassword(this.requestGeneratedPassword$), + this.clearGeneratedPassword$.pipe(mapTo(null)), + ); + this.yieldedPassword$ .pipe( - tap(async ({ credential }) => { - this.generatedPassword = credential; - await this.addPasswordCallback(this.generatedPassword); + concatMap(async (generated) => { + await this.trackCredentialHistory(generated.credential); + return generated.credential; }), ) - .subscribe(); + .subscribe(this.credential$); } /** @@ -340,7 +349,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { delete this.portKeyForTab[tabId]; } - this.generatedPassword = null; + this.clearGeneratedPassword(); this.focusedFieldData = null; } @@ -1338,7 +1347,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { const command = "closeAutofillInlineMenu"; const sendOptions = { frameId: 0 }; const updateVisibilityDefaults = { overlayElement, isVisible: false, forceUpdate: true }; - this.generatedPassword = null; + this.clearGeneratedPassword(); if (forceCloseInlineMenu) { BrowserApi.tabSendMessage(sender.tab, { command, overlayElement }, sendOptions).catch( @@ -1817,19 +1826,26 @@ export class OverlayBackground implements OverlayBackgroundInterface { this.requestGeneratedPassword$.next({ source, type: Type.password }); } + /** + * Clears generated password. + */ + private clearGeneratedPassword() { + this.clearGeneratedPassword$.next(); + } + /** * Updates the generated password in the inline menu list. * * @param refreshPassword - Identifies whether the generated password should be refreshed */ private async updateGeneratedPassword(refreshPassword: boolean = false) { - if (!this.generatedPassword || refreshPassword) { + if (!this.credential$.value || refreshPassword) { this.requestGeneratedPassword("inline-menu"); } this.postMessageToPort(this.inlineMenuListPort, { command: "updateAutofillInlineMenuGeneratedPassword", - generatedPassword: this.generatedPassword, + generatedPassword: this.credential$.value, refreshPassword, }); } @@ -1841,7 +1857,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { * @param port - The port of the sender */ private async fillGeneratedPassword(port: chrome.runtime.Port) { - if (!this.generatedPassword) { + if (!this.credential$.value) { return; } @@ -1866,7 +1882,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { const cipher = this.buildLoginCipherView({ username: "", - password: this.generatedPassword, + password: this.credential$.value, hostname: "", uri: "", }); @@ -2989,7 +3005,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { : AutofillOverlayPort.ButtonMessageConnector, inlineMenuFillType: this.focusedFieldData?.inlineMenuFillType, showPasskeysLabels: this.showPasskeysLabelsWithinInlineMenu, - generatedPassword: showInlineMenuPasswordGenerator ? this.generatedPassword : null, + generatedPassword: showInlineMenuPasswordGenerator ? this.credential$.value : null, showSaveLoginMenu, showInlineMenuAccountCreation, authStatus, @@ -3088,8 +3104,8 @@ export class OverlayBackground implements OverlayBackgroundInterface { return false; } - if (!this.generatedPassword) { - this.requestGeneratedPassword("inline-menu"); + if (!this.credential$.value) { + this.requestGeneratedPassword("inline-menu.init"); } return true;