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:
@@ -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");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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],
|
||||||
|
|||||||
@@ -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>
|
|
||||||
|
|||||||
@@ -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",
|
|
||||||
});
|
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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",
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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'"
|
||||||
|
|||||||
Reference in New Issue
Block a user