1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-12 14:23:32 +00:00

[PM-25216] - handle extension already installed on new user creation (#16650)

* handle extension already installed on new user creation

* fix tests

* remove comment
This commit is contained in:
Jordan Aasen
2025-10-01 13:21:11 -07:00
committed by GitHub
parent 5de8a145ec
commit 688647b2c6
6 changed files with 32 additions and 33 deletions

View File

@@ -29,15 +29,32 @@
</div> </div>
</section> </section>
<section *ngIf="state === SetupExtensionState.Success" class="tw-flex tw-flex-col tw-items-center"> <section
*ngIf="state === SetupExtensionState.Success || state === SetupExtensionState.AlreadyInstalled"
class="tw-flex tw-flex-col tw-items-center"
>
<div class="tw-size-[90px]"> <div class="tw-size-[90px]">
<bit-icon [icon]="PartyIcon"></bit-icon> <bit-icon [icon]="PartyIcon"></bit-icon>
</div> </div>
<h1 bitTypography="h2" class="tw-mb-6 tw-mt-4">{{ "bitwardenExtensionInstalled" | i18n }}</h1> <h1 bitTypography="h2" class="tw-mb-6 tw-mt-4 tw-text-center">
{{
(state === SetupExtensionState.Success
? "bitwardenExtensionInstalled"
: "openTheBitwardenExtension"
) | i18n
}}
</h1>
<div <div
class="tw-flex tw-flex-col tw-rounded-2xl tw-bg-background tw-border tw-border-solid tw-border-secondary-300 tw-p-8" class="tw-flex tw-flex-col tw-rounded-2xl tw-bg-background tw-border tw-border-solid tw-border-secondary-300 tw-p-8 tw-max-w-md tw-text-center"
> >
<p>{{ "openExtensionToAutofill" | i18n }}</p> <p>
{{
(state === SetupExtensionState.Success
? "openExtensionToAutofill"
: "bitwardenExtensionInstalledOpenExtension"
) | i18n
}}
</p>
<button type="button" bitButton buttonType="primary" class="tw-mb-2" (click)="openExtension()"> <button type="button" bitButton buttonType="primary" class="tw-mb-2" (click)="openExtension()">
{{ "openBitwardenExtension" | i18n }} {{ "openBitwardenExtension" | i18n }}
</button> </button>

View File

@@ -93,14 +93,9 @@ describe("SetupExtensionComponent", () => {
}); });
describe("extensionInstalled$", () => { describe("extensionInstalled$", () => {
it("redirects the user to the vault when the first emitted value is true", () => {
extensionInstalled$.next(true);
expect(navigate).toHaveBeenCalledWith(["/vault"]);
});
describe("success state", () => { describe("success state", () => {
beforeEach(() => { beforeEach(() => {
update.mockClear();
// avoid initial redirect // avoid initial redirect
extensionInstalled$.next(false); extensionInstalled$.next(false);

View File

@@ -36,6 +36,7 @@ export const SetupExtensionState = {
Loading: "loading", Loading: "loading",
NeedsExtension: "needs-extension", NeedsExtension: "needs-extension",
Success: "success", Success: "success",
AlreadyInstalled: "already-installed",
ManualOpen: "manual-open", ManualOpen: "manual-open",
} as const; } as const;
@@ -99,9 +100,10 @@ export class SetupExtensionComponent implements OnInit, OnDestroy {
this.webBrowserExtensionInteractionService.extensionInstalled$ this.webBrowserExtensionInteractionService.extensionInstalled$
.pipe(takeUntilDestroyed(this.destroyRef), startWith(null), pairwise()) .pipe(takeUntilDestroyed(this.destroyRef), startWith(null), pairwise())
.subscribe(([previousState, currentState]) => { .subscribe(([previousState, currentState]) => {
// Initial state transitioned to extension installed, redirect the user // User landed on the page and the extension is already installed, show already installed state
if (previousState === null && currentState) { if (previousState === null && currentState) {
void this.router.navigate(["/vault"]); void this.dismissExtensionPage();
this.state = SetupExtensionState.AlreadyInstalled;
} }
// Extension was not installed and now it is, show success state // Extension was not installed and now it is, show success state

View File

@@ -82,13 +82,6 @@ describe("setupExtensionRedirectGuard", () => {
expect(await setupExtensionGuard()).toBe(true); expect(await setupExtensionGuard()).toBe(true);
}); });
it("returns `true` when the user has the extension installed", async () => {
state$.next(false);
extensionInstalled$.next(true);
expect(await setupExtensionGuard()).toBe(true);
});
it('redirects the user to "/setup-extension" when all criteria do not pass', async () => { it('redirects the user to "/setup-extension" when all criteria do not pass', async () => {
state$.next(false); state$.next(false);
extensionInstalled$.next(false); extensionInstalled$.next(false);

View File

@@ -11,8 +11,6 @@ import {
UserKeyDefinition, UserKeyDefinition,
} from "@bitwarden/common/platform/state"; } from "@bitwarden/common/platform/state";
import { WebBrowserInteractionService } from "../services/web-browser-interaction.service";
export const SETUP_EXTENSION_DISMISSED = new UserKeyDefinition<boolean>( export const SETUP_EXTENSION_DISMISSED = new UserKeyDefinition<boolean>(
SETUP_EXTENSION_DISMISSED_DISK, SETUP_EXTENSION_DISMISSED_DISK,
"setupExtensionDismissed", "setupExtensionDismissed",
@@ -27,7 +25,6 @@ export const setupExtensionRedirectGuard: CanActivateFn = async () => {
const accountService = inject(AccountService); const accountService = inject(AccountService);
const vaultProfileService = inject(VaultProfileService); const vaultProfileService = inject(VaultProfileService);
const stateProvider = inject(StateProvider); const stateProvider = inject(StateProvider);
const webBrowserInteractionService = inject(WebBrowserInteractionService);
const isMobile = Utils.isMobileBrowser; const isMobile = Utils.isMobileBrowser;
@@ -43,10 +40,6 @@ export const setupExtensionRedirectGuard: CanActivateFn = async () => {
return router.createUrlTree(["/login"]); return router.createUrlTree(["/login"]);
} }
const hasExtensionInstalledPromise = firstValueFrom(
webBrowserInteractionService.extensionInstalled$,
);
const dismissedExtensionPage = await firstValueFrom( const dismissedExtensionPage = await firstValueFrom(
stateProvider stateProvider
.getUser(currentAcct.id, SETUP_EXTENSION_DISMISSED) .getUser(currentAcct.id, SETUP_EXTENSION_DISMISSED)
@@ -66,13 +59,6 @@ export const setupExtensionRedirectGuard: CanActivateFn = async () => {
return true; return true;
} }
// Checking for the extension is a more expensive operation, do it last to avoid unnecessary delays.
const hasExtensionInstalled = await hasExtensionInstalledPromise;
if (hasExtensionInstalled) {
return true;
}
return router.createUrlTree(["/setup-extension"]); return router.createUrlTree(["/setup-extension"]);
}; };

View File

@@ -11199,6 +11199,12 @@
"bitwardenExtensionInstalled": { "bitwardenExtensionInstalled": {
"message": "Bitwarden extension installed!" "message": "Bitwarden extension installed!"
}, },
"openTheBitwardenExtension": {
"message": "Open the Bitwarden extension"
},
"bitwardenExtensionInstalledOpenExtension": {
"message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling."
},
"openExtensionToAutofill": { "openExtensionToAutofill": {
"message": "Open the extension to log in and start autofilling." "message": "Open the extension to log in and start autofilling."
}, },