1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 22:03:36 +00:00

[PM-24505] Manually open extension error message (#17116)

* update manual open message to be more generic to cover more scenarios

* update error state when attempting to open the extension via button press
This commit is contained in:
Nick Krantz
2025-11-21 14:48:50 -06:00
committed by GitHub
parent 129c21cfb8
commit aa2d263751
8 changed files with 44 additions and 68 deletions

View File

@@ -166,8 +166,8 @@ describe("BrowserExtensionPromptComponent", () => {
it("shows manual open error message", () => { it("shows manual open error message", () => {
const manualText = fixture.debugElement.query(By.css("p")).nativeElement; const manualText = fixture.debugElement.query(By.css("p")).nativeElement;
expect(manualText.textContent.trim()).toContain("openExtensionManuallyPart1"); expect(manualText.textContent.trim()).toContain("openExtensionFromToolbarPart1");
expect(manualText.textContent.trim()).toContain("openExtensionManuallyPart2"); expect(manualText.textContent.trim()).toContain("openExtensionFromToolbarPart2");
}); });
}); });
}); });

View File

@@ -1,5 +1,5 @@
import { CommonModule, DOCUMENT } from "@angular/common"; import { CommonModule, DOCUMENT } from "@angular/common";
import { Component, Inject, OnDestroy, OnInit } from "@angular/core"; import { Component, Inject, OnDestroy, OnInit, ChangeDetectionStrategy } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute } from "@angular/router";
import { map, Observable, of, tap } from "rxjs"; import { map, Observable, of, tap } from "rxjs";
@@ -14,12 +14,11 @@ import {
} from "../../services/browser-extension-prompt.service"; } from "../../services/browser-extension-prompt.service";
import { ManuallyOpenExtensionComponent } from "../manually-open-extension/manually-open-extension.component"; import { ManuallyOpenExtensionComponent } from "../manually-open-extension/manually-open-extension.component";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({ @Component({
selector: "vault-browser-extension-prompt", selector: "vault-browser-extension-prompt",
templateUrl: "./browser-extension-prompt.component.html", templateUrl: "./browser-extension-prompt.component.html",
imports: [CommonModule, I18nPipe, ButtonComponent, IconModule, ManuallyOpenExtensionComponent], imports: [CommonModule, I18nPipe, ButtonComponent, IconModule, ManuallyOpenExtensionComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class BrowserExtensionPromptComponent implements OnInit, OnDestroy { export class BrowserExtensionPromptComponent implements OnInit, OnDestroy {
protected VaultMessages = VaultMessages; protected VaultMessages = VaultMessages;

View File

@@ -1,8 +1,8 @@
<p bitTypography="body1" class="tw-mb-0 tw-text-xl"> <p bitTypography="body1" class="tw-mb-0">
{{ "openExtensionManuallyPart1" | i18n }} {{ "openExtensionFromToolbarPart1" | i18n }}
<bit-icon <bit-icon
[icon]="BitwardenIcon" [icon]="BitwardenIcon"
class="[&>svg]:tw-align-baseline [&>svg]:-tw-mb-[2px]" class="!tw-inline-block [&>svg]:tw-align-baseline [&>svg]:-tw-mb-[0.25rem]"
></bit-icon> ></bit-icon>
{{ "openExtensionManuallyPart2" | i18n }} {{ "openExtensionFromToolbarPart2" | i18n }}
</p> </p>

View File

@@ -1,12 +1,11 @@
import { Component } from "@angular/core"; import { Component, ChangeDetectionStrategy } from "@angular/core";
import { BitwardenIcon } from "@bitwarden/assets/svg"; import { BitwardenIcon } from "@bitwarden/assets/svg";
import { IconModule } from "@bitwarden/components"; import { IconModule } from "@bitwarden/components";
import { I18nPipe } from "@bitwarden/ui-common"; import { I18nPipe } from "@bitwarden/ui-common";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: "vault-manually-open-extension", selector: "vault-manually-open-extension",
templateUrl: "./manually-open-extension.component.html", templateUrl: "./manually-open-extension.component.html",
imports: [I18nPipe, IconModule], imports: [I18nPipe, IconModule],

View File

@@ -29,10 +29,7 @@
</div> </div>
</section> </section>
<section <section *ngIf="showSuccessUI" class="tw-flex tw-flex-col tw-items-center">
*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>
@@ -40,20 +37,15 @@
{{ {{
(state === SetupExtensionState.Success (state === SetupExtensionState.Success
? "bitwardenExtensionInstalled" ? "bitwardenExtensionInstalled"
: "openTheBitwardenExtension" : "bitwardenExtensionIsInstalled"
) | i18n ) | i18n
}} }}
</h1> </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 tw-max-w-md tw-text-center" 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 md:tw-w-[28rem] tw-text-center"
> >
<p> <p>
{{ {{ "openExtensionToAutofill" | i18n }}
(state === SetupExtensionState.Success
? "openExtensionToAutofill"
: "bitwardenExtensionInstalledOpenExtension"
) | i18n
}}
</p> </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 }}
@@ -61,8 +53,16 @@
<a bitButton buttonType="secondary" routerLink="/vault"> <a bitButton buttonType="secondary" routerLink="/vault">
{{ "skipToWebApp" | i18n }} {{ "skipToWebApp" | i18n }}
</a> </a>
<div
*ngIf="state === SetupExtensionState.ManualOpen"
aria-live="polite"
class="tw-text-center tw-mt-4"
>
<vault-manually-open-extension></vault-manually-open-extension>
</div> </div>
<p class="tw-mt-10 tw-max-w-96 tw-text-center"> </div>
<p class="tw-mt-4 tw-max-w-96 tw-text-center">
{{ "gettingStartedWithBitwardenPart1" | i18n }} {{ "gettingStartedWithBitwardenPart1" | i18n }}
<a bitLink href="https://bitwarden.com/help/learning-center/"> <a bitLink href="https://bitwarden.com/help/learning-center/">
{{ "gettingStartedWithBitwardenPart2" | i18n }} {{ "gettingStartedWithBitwardenPart2" | i18n }}
@@ -73,7 +73,3 @@
</a> </a>
</p> </p>
</section> </section>
<section *ngIf="state === SetupExtensionState.ManualOpen" aria-live="polite" class="tw-text-center">
<vault-manually-open-extension></vault-manually-open-extension>
</section>

View File

@@ -4,7 +4,6 @@ import { Router, RouterModule } from "@angular/router";
import { BehaviorSubject } from "rxjs"; import { BehaviorSubject } from "rxjs";
import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens"; import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens";
import { BrowserExtensionIcon } from "@bitwarden/assets/svg";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { DeviceType } from "@bitwarden/common/enums"; import { DeviceType } from "@bitwarden/common/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -12,7 +11,6 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { StateProvider } from "@bitwarden/common/platform/state"; import { StateProvider } from "@bitwarden/common/platform/state";
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import { AnonLayoutWrapperDataService } from "@bitwarden/components";
import { WebBrowserInteractionService } from "../../services/web-browser-interaction.service"; import { WebBrowserInteractionService } from "../../services/web-browser-interaction.service";
@@ -25,14 +23,12 @@ describe("SetupExtensionComponent", () => {
const navigate = jest.fn().mockResolvedValue(true); const navigate = jest.fn().mockResolvedValue(true);
const openExtension = jest.fn().mockResolvedValue(true); const openExtension = jest.fn().mockResolvedValue(true);
const update = jest.fn().mockResolvedValue(true); const update = jest.fn().mockResolvedValue(true);
const setAnonLayoutWrapperData = jest.fn();
const extensionInstalled$ = new BehaviorSubject<boolean | null>(null); const extensionInstalled$ = new BehaviorSubject<boolean | null>(null);
beforeEach(async () => { beforeEach(async () => {
navigate.mockClear(); navigate.mockClear();
openExtension.mockClear(); openExtension.mockClear();
update.mockClear(); update.mockClear();
setAnonLayoutWrapperData.mockClear();
window.matchMedia = jest.fn().mockReturnValue(false); window.matchMedia = jest.fn().mockReturnValue(false);
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
@@ -43,7 +39,6 @@ describe("SetupExtensionComponent", () => {
{ provide: PlatformUtilsService, useValue: { getDevice: () => DeviceType.UnknownBrowser } }, { provide: PlatformUtilsService, useValue: { getDevice: () => DeviceType.UnknownBrowser } },
{ provide: SYSTEM_THEME_OBSERVABLE, useValue: new BehaviorSubject("system") }, { provide: SYSTEM_THEME_OBSERVABLE, useValue: new BehaviorSubject("system") },
{ provide: ThemeStateService, useValue: { selectedTheme$: new BehaviorSubject("system") } }, { provide: ThemeStateService, useValue: { selectedTheme$: new BehaviorSubject("system") } },
{ provide: AnonLayoutWrapperDataService, useValue: { setAnonLayoutWrapperData } },
{ {
provide: AccountService, provide: AccountService,
useValue: { activeAccount$: new BehaviorSubject({ account: { id: "account-id" } }) }, useValue: { activeAccount$: new BehaviorSubject({ account: { id: "account-id" } }) },
@@ -133,14 +128,6 @@ describe("SetupExtensionComponent", () => {
tick(); tick();
expect(component["state"]).toBe(SetupExtensionState.ManualOpen); expect(component["state"]).toBe(SetupExtensionState.ManualOpen);
expect(setAnonLayoutWrapperData).toHaveBeenCalledWith({
pageTitle: {
key: "somethingWentWrong",
},
pageIcon: BrowserExtensionIcon,
hideCardWrapper: false,
maxWidth: "md",
});
})); }));
}); });
}); });

View File

@@ -5,7 +5,7 @@ import { Router, RouterModule } from "@angular/router";
import { firstValueFrom, pairwise, startWith } from "rxjs"; import { firstValueFrom, pairwise, startWith } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
import { BrowserExtensionIcon, Party } from "@bitwarden/assets/svg"; import { Party } from "@bitwarden/assets/svg";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -14,7 +14,6 @@ import { StateProvider } from "@bitwarden/common/platform/state";
import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values"; import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values";
import { getWebStoreUrl } from "@bitwarden/common/vault/utils/get-web-store-url"; import { getWebStoreUrl } from "@bitwarden/common/vault/utils/get-web-store-url";
import { import {
AnonLayoutWrapperDataService,
ButtonComponent, ButtonComponent,
CenterPositionStrategy, CenterPositionStrategy,
DialogRef, DialogRef,
@@ -68,7 +67,6 @@ export class SetupExtensionComponent implements OnInit, OnDestroy {
private stateProvider = inject(StateProvider); private stateProvider = inject(StateProvider);
private accountService = inject(AccountService); private accountService = inject(AccountService);
private document = inject(DOCUMENT); private document = inject(DOCUMENT);
private anonLayoutWrapperDataService = inject(AnonLayoutWrapperDataService);
protected SetupExtensionState = SetupExtensionState; protected SetupExtensionState = SetupExtensionState;
protected PartyIcon = Party; protected PartyIcon = Party;
@@ -144,6 +142,16 @@ export class SetupExtensionComponent implements OnInit, OnDestroy {
} }
} }
get showSuccessUI(): boolean {
const successStates = [
SetupExtensionState.Success,
SetupExtensionState.AlreadyInstalled,
SetupExtensionState.ManualOpen,
] as string[];
return successStates.includes(this.state);
}
/** Opens the add extension later dialog */ /** Opens the add extension later dialog */
addItLater() { addItLater() {
this.dialogRef = this.dialogService.open<unknown, AddExtensionLaterDialogData>( this.dialogRef = this.dialogService.open<unknown, AddExtensionLaterDialogData>(
@@ -161,16 +169,6 @@ export class SetupExtensionComponent implements OnInit, OnDestroy {
async openExtension() { async openExtension() {
await this.webBrowserExtensionInteractionService.openExtension().catch(() => { await this.webBrowserExtensionInteractionService.openExtension().catch(() => {
this.state = SetupExtensionState.ManualOpen; this.state = SetupExtensionState.ManualOpen;
// Update the anon layout data to show the proper error design
this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({
pageTitle: {
key: "somethingWentWrong",
},
pageIcon: BrowserExtensionIcon,
hideCardWrapper: false,
maxWidth: "md",
});
}); });
} }

View File

@@ -11351,14 +11351,6 @@
"openedExtensionViewAtRiskPasswords": { "openedExtensionViewAtRiskPasswords": {
"message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords."
}, },
"openExtensionManuallyPart1": {
"message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon",
"description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'"
},
"openExtensionManuallyPart2": {
"message": "from the toolbar.",
"description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'"
},
"resellerRenewalWarningMsg": { "resellerRenewalWarningMsg": {
"message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.",
"placeholders": { "placeholders": {
@@ -11679,11 +11671,8 @@
"bitwardenExtensionInstalled": { "bitwardenExtensionInstalled": {
"message": "Bitwarden extension installed!" "message": "Bitwarden extension installed!"
}, },
"openTheBitwardenExtension": { "bitwardenExtensionIsInstalled": {
"message": "Open the Bitwarden extension" "message": "Bitwarden extension is installed!"
},
"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."
@@ -11699,6 +11688,14 @@
"message": "Learning Center", "message": "Learning Center",
"description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'"
}, },
"openExtensionFromToolbarPart1": {
"message": "If the extension didn't open, you may need to open Bitwarden from the icon ",
"description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'"
},
"openExtensionFromToolbarPart2": {
"message": " on the toolbar.",
"description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'"
},
"gettingStartedWithBitwardenPart3": { "gettingStartedWithBitwardenPart3": {
"message": "Help Center", "message": "Help Center",
"description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'"