diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml
index 23f4bd35f10..f924c5c98ea 100644
--- a/.github/workflows/build-browser.yml
+++ b/.github/workflows/build-browser.yml
@@ -164,6 +164,10 @@ jobs:
run: npm run dist:mv3
working-directory: browser-source/apps/browser
+ - name: Build Chrome Manifest v3 Beta
+ run: npm run dist:chrome:beta
+ working-directory: browser-source/apps/browser
+
- name: Gulp
run: gulp ci
working-directory: browser-source/apps/browser
@@ -196,6 +200,13 @@ jobs:
path: browser-source/apps/browser/dist/dist-chrome-mv3.zip
if-no-files-found: error
+ - name: Upload Chrome MV3 Beta artifact (DO NOT USE FOR PROD)
+ uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
+ with:
+ name: DO-NOT-USE-FOR-PROD-dist-chrome-MV3-beta-${{ env._BUILD_NUMBER }}.zip
+ path: browser-source/apps/browser/dist/dist-chrome-mv3-beta.zip
+ if-no-files-found: error
+
- name: Upload Firefox artifact
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
diff --git a/.github/workflows/deploy-web.yml b/.github/workflows/deploy-web.yml
index 6a5d9f14057..b034136f585 100644
--- a/.github/workflows/deploy-web.yml
+++ b/.github/workflows/deploy-web.yml
@@ -230,6 +230,17 @@ jobs:
url: https://github.com/bitwarden/clients/actions/runs/${{ github.run_id }}
AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
+ update-summary:
+ name: Display commit
+ needs: artifact-check
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Display commit SHA
+ run: |
+ REPO_URL="https://github.com/bitwarden/clients/commit"
+ COMMIT_SHA="${{ needs.artifact-check.outputs.artifact-build-commit }}"
+ echo ":steam_locomotive: View [commit]($REPO_URL/$COMMIT_SHA)" >> $GITHUB_STEP_SUMMARY
+
azure-deploy:
name: Deploy Web Vault to ${{ inputs.environment }} Storage Account
needs:
diff --git a/.storybook/main.ts b/.storybook/main.ts
index c71a74c2a73..cb63ada550b 100644
--- a/.storybook/main.ts
+++ b/.storybook/main.ts
@@ -9,6 +9,8 @@ const config: StorybookConfig = {
"../libs/components/src/**/*.stories.@(js|jsx|ts|tsx)",
"../apps/web/src/**/*.mdx",
"../apps/web/src/**/*.stories.@(js|jsx|ts|tsx)",
+ "../apps/browser/src/**/*.mdx",
+ "../apps/browser/src/**/*.stories.@(js|jsx|ts|tsx)",
"../bitwarden_license/bit-web/src/**/*.mdx",
"../bitwarden_license/bit-web/src/**/*.stories.@(js|jsx|ts|tsx)",
],
diff --git a/.storybook/tsconfig.json b/.storybook/tsconfig.json
index 113cc5bcde5..34acc9a740c 100644
--- a/.storybook/tsconfig.json
+++ b/.storybook/tsconfig.json
@@ -1,12 +1,10 @@
{
"extends": "../tsconfig",
"compilerOptions": {
- "types": ["node", "jest", "chrome"],
"allowSyntheticDefaultImports": true
},
- "exclude": ["../src/test.setup.ts", "../apps/src/**/*.spec.ts", "../libs/**/*.spec.ts"],
+ "exclude": ["../src/test.setup.ts", "../apps/**/*.spec.ts", "../libs/**/*.spec.ts"],
"files": [
- "./typings.d.ts",
"./preview.tsx",
"../libs/components/src/main.ts",
"../libs/components/src/polyfills.ts"
diff --git a/.storybook/typings.d.ts b/.storybook/typings.d.ts
deleted file mode 100644
index c94d67b1a26..00000000000
--- a/.storybook/typings.d.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-declare module "*.md" {
- const content: string;
- export default content;
-}
diff --git a/apps/browser/gulpfile.js b/apps/browser/gulpfile.js
index 6a0980fc27f..d5b29ffc388 100644
--- a/apps/browser/gulpfile.js
+++ b/apps/browser/gulpfile.js
@@ -35,6 +35,9 @@ function buildString() {
if (process.env.MANIFEST_VERSION) {
build = `-mv${process.env.MANIFEST_VERSION}`;
}
+ if (process.env.BETA_BUILD === "1") {
+ build += "-beta";
+ }
if (process.env.BUILD_NUMBER && process.env.BUILD_NUMBER !== "") {
build = `-${process.env.BUILD_NUMBER}`;
}
@@ -65,6 +68,9 @@ function distFirefox() {
manifest.optional_permissions = manifest.optional_permissions.filter(
(permission) => permission !== "privacy",
);
+ if (process.env.BETA_BUILD === "1") {
+ manifest = applyBetaLabels(manifest);
+ }
return manifest;
});
}
@@ -72,6 +78,9 @@ function distFirefox() {
function distOpera() {
return dist("opera", (manifest) => {
delete manifest.applications;
+ if (process.env.BETA_BUILD === "1") {
+ manifest = applyBetaLabels(manifest);
+ }
return manifest;
});
}
@@ -81,6 +90,9 @@ function distChrome() {
delete manifest.applications;
delete manifest.sidebar_action;
delete manifest.commands._execute_sidebar_action;
+ if (process.env.BETA_BUILD === "1") {
+ manifest = applyBetaLabels(manifest);
+ }
return manifest;
});
}
@@ -90,6 +102,9 @@ function distEdge() {
delete manifest.applications;
delete manifest.sidebar_action;
delete manifest.commands._execute_sidebar_action;
+ if (process.env.BETA_BUILD === "1") {
+ manifest = applyBetaLabels(manifest);
+ }
return manifest;
});
}
@@ -210,6 +225,9 @@ async function safariCopyBuild(source, dest) {
delete manifest.commands._execute_sidebar_action;
delete manifest.optional_permissions;
manifest.permissions.push("nativeMessaging");
+ if (process.env.BETA_BUILD === "1") {
+ manifest = applyBetaLabels(manifest);
+ }
return manifest;
}),
),
@@ -235,6 +253,19 @@ async function ciCoverage(cb) {
.pipe(gulp.dest(paths.coverage));
}
+function applyBetaLabels(manifest) {
+ manifest.name = "Bitwarden Password Manager BETA";
+ manifest.short_name = "Bitwarden BETA";
+ manifest.description = "THIS EXTENSION IS FOR BETA TESTING BITWARDEN.";
+ if (process.env.GITHUB_RUN_ID) {
+ manifest.version_name = `${manifest.version} beta - ${process.env.GITHUB_SHA.slice(0, 8)}`;
+ manifest.version = `${manifest.version}.${parseInt(process.env.GITHUB_RUN_ID.slice(-4))}`;
+ } else {
+ manifest.version = `${manifest.version}.0`;
+ }
+ return manifest;
+}
+
exports["dist:firefox"] = distFirefox;
exports["dist:chrome"] = distChrome;
exports["dist:opera"] = distOpera;
diff --git a/apps/browser/package.json b/apps/browser/package.json
index 506f19f279b..580acfc3d02 100644
--- a/apps/browser/package.json
+++ b/apps/browser/package.json
@@ -7,10 +7,14 @@
"build:watch": "webpack --watch",
"build:watch:mv3": "cross-env MANIFEST_VERSION=3 webpack --watch",
"build:prod": "cross-env NODE_ENV=production webpack",
+ "build:prod:beta": "cross-env BETA_BUILD=1 NODE_ENV=production webpack",
"build:prod:watch": "cross-env NODE_ENV=production webpack --watch",
"dist": "npm run build:prod && gulp dist",
+ "dist:beta": "npm run build:prod:beta && cross-env BETA_BUILD=1 gulp dist",
"dist:mv3": "cross-env MANIFEST_VERSION=3 npm run build:prod && cross-env MANIFEST_VERSION=3 gulp dist",
+ "dist:mv3:beta": "cross-env MANIFEST_VERSION=3 npm run build:prod:beta && cross-env MANIFEST_VERSION=3 BETA_BUILD=1 gulp dist",
"dist:chrome": "npm run build:prod && gulp dist:chrome",
+ "dist:chrome:beta": "cross-env MANIFEST_VERSION=3 npm run build:prod:beta && cross-env MANIFEST_VERSION=3 BETA_BUILD=1 gulp dist:chrome",
"dist:firefox": "npm run build:prod && gulp dist:firefox",
"dist:opera": "npm run build:prod && gulp dist:opera",
"dist:safari": "npm run build:prod && gulp dist:safari",
diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json
index d3d9106c15e..1a56d32a35a 100644
--- a/apps/browser/src/_locales/pl/messages.json
+++ b/apps/browser/src/_locales/pl/messages.json
@@ -7,7 +7,7 @@
"description": "Extension name, MUST be less than 40 characters (Safari restriction)"
},
"extDesc": {
- "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information",
+ "message": "W domu, w pracy, lub w ruchu, Bitwarden z łatwością zabezpiecza Twoje hasła, passkeys i poufne informacje",
"description": "Extension description, MUST be less than 112 characters (Safari restriction)"
},
"loginOrCreateNewAccount": {
diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json
index 417bc977ebe..0f40bc63bb0 100644
--- a/apps/browser/src/_locales/pt_BR/messages.json
+++ b/apps/browser/src/_locales/pt_BR/messages.json
@@ -7,7 +7,7 @@
"description": "Extension name, MUST be less than 40 characters (Safari restriction)"
},
"extDesc": {
- "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information",
+ "message": "Em qual lugar for, o Bitwarden protege suas senhas, chaves de acesso, e informações confidenciais",
"description": "Extension description, MUST be less than 112 characters (Safari restriction)"
},
"loginOrCreateNewAccount": {
diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json
index 4eba4ffaeaa..6e530412db6 100644
--- a/apps/browser/src/_locales/vi/messages.json
+++ b/apps/browser/src/_locales/vi/messages.json
@@ -3,11 +3,11 @@
"message": "Bitwarden"
},
"extName": {
- "message": "Bitwarden Password Manager",
+ "message": "Bitwarden - Trình Quản lý Mật khẩu",
"description": "Extension name, MUST be less than 40 characters (Safari restriction)"
},
"extDesc": {
- "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information",
+ "message": "Ở nhà, ở cơ quan, hay trên đường đi, Bitwarden sẽ bảo mật tất cả mật khẩu, passkey, và thông tin cá nhân của bạn",
"description": "Extension description, MUST be less than 112 characters (Safari restriction)"
},
"loginOrCreateNewAccount": {
@@ -650,7 +650,7 @@
"message": "'Thông báo Thêm đăng nhập' sẽ tự động nhắc bạn lưu các đăng nhập mới vào hầm an toàn của bạn bất cứ khi nào bạn đăng nhập trang web lần đầu tiên."
},
"addLoginNotificationDescAlt": {
- "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts."
+ "message": "Đưa ra lựa chọn để thêm một mục nếu không tìm thấy mục đó trong hòm của bạn. Áp dụng với mọi tài khoản đăng nhập trên thiết bị."
},
"showCardsCurrentTab": {
"message": "Hiển thị thẻ trên trang Tab"
@@ -685,13 +685,13 @@
"message": "Yêu cầu cập nhật mật khẩu đăng nhập khi phát hiện thay đổi trên trang web."
},
"changedPasswordNotificationDescAlt": {
- "message": "Ask to update a login's password when a change is detected on a website. Applies to all logged in accounts."
+ "message": "Đưa ra lựa chọn để cập nhật mật khẩu khi phát hiện có sự thay đổi trên trang web. Áp dụng với mọi tài khoản đăng nhập trên thiết bị."
},
"enableUsePasskeys": {
- "message": "Ask to save and use passkeys"
+ "message": "Đưa ra lựa chọn để lưu và sử dụng passkey"
},
"usePasskeysDesc": {
- "message": "Ask to save new passkeys or log in with passkeys stored in your vault. Applies to all logged in accounts."
+ "message": "Đưa ra lựa chọn để lưu passkey mới hoặc đăng nhập bằng passkey đã lưu trong hòm. Áp dụng với mọi tài khoản đăng nhập trên thiết bị."
},
"notificationChangeDesc": {
"message": "Bạn có muốn cập nhật mật khẩu này trên Bitwarden không?"
@@ -712,7 +712,7 @@
"message": "Sử dụng một đúp chuột để truy cập vào việc tạo mật khẩu và thông tin đăng nhập phù hợp cho trang web. "
},
"contextMenuItemDescAlt": {
- "message": "Use a secondary click to access password generation and matching logins for the website. Applies to all logged in accounts."
+ "message": "Truy cập trình khởi tạo mật khẩu và các mục đăng nhập đã lưu của trang web bằng cách nhấn đúp chuột. Áp dụng với mọi tài khoản đăng nhập trên thiết bị."
},
"defaultUriMatchDetection": {
"message": "Phương thức kiểm tra URI mặc định",
@@ -728,7 +728,7 @@
"message": "Thay đổi màu sắc ứng dụng."
},
"themeDescAlt": {
- "message": "Change the application's color theme. Applies to all logged in accounts."
+ "message": "Thay đổi tông màu giao diện của ứng dụng. Áp dụng với mọi tài khoản đăng nhập trên thiết bị."
},
"dark": {
"message": "Tối",
@@ -1061,10 +1061,10 @@
"message": "Tắt cài đặt trình quản lý mật khẩu tích hợp trong trình duyệt của bạn để tránh xung đột."
},
"turnOffBrowserBuiltInPasswordManagerSettingsLink": {
- "message": "Edit browser settings."
+ "message": "Thay đổi cài đặt của trình duyệt."
},
"autofillOverlayVisibilityOff": {
- "message": "Off",
+ "message": "Tắt",
"description": "Overlay setting select option for disabling autofill overlay"
},
"autofillOverlayVisibilityOnFieldFocus": {
@@ -1168,7 +1168,7 @@
"message": "Hiển thị một ảnh nhận dạng bên cạnh mỗi lần đăng nhập."
},
"faviconDescAlt": {
- "message": "Show a recognizable image next to each login. Applies to all logged in accounts."
+ "message": "Hiển thị một biểu tượng dễ nhận dạng bên cạnh mỗi mục đăng nhập. Áp dụng với mọi tài khoản đăng nhập trên thiết bị."
},
"enableBadgeCounter": {
"message": "Hiển thị biểu tượng bộ đếm"
@@ -1500,7 +1500,7 @@
"message": "Mã PIN không hợp lệ."
},
"tooManyInvalidPinEntryAttemptsLoggingOut": {
- "message": "Too many invalid PIN entry attempts. Logging out."
+ "message": "Mã PIN bị gõ sai quá nhiều lần. Đang đăng xuất."
},
"unlockWithBiometrics": {
"message": "Mở khóa bằng sinh trắc học"
diff --git a/apps/browser/src/auth/background/service-factories/two-factor-service.factory.ts b/apps/browser/src/auth/background/service-factories/two-factor-service.factory.ts
index 1d79bbbaf1d..5af5eb00177 100644
--- a/apps/browser/src/auth/background/service-factories/two-factor-service.factory.ts
+++ b/apps/browser/src/auth/background/service-factories/two-factor-service.factory.ts
@@ -1,11 +1,13 @@
import { TwoFactorService as AbstractTwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { TwoFactorService } from "@bitwarden/common/auth/services/two-factor.service";
+import { GlobalStateProvider } from "@bitwarden/common/platform/state";
import {
FactoryOptions,
CachedServices,
factory,
} from "../../../platform/background/service-factories/factory-options";
+import { globalStateProviderFactory } from "../../../platform/background/service-factories/global-state-provider.factory";
import {
I18nServiceInitOptions,
i18nServiceFactory,
@@ -19,7 +21,8 @@ type TwoFactorServiceFactoryOptions = FactoryOptions;
export type TwoFactorServiceInitOptions = TwoFactorServiceFactoryOptions &
I18nServiceInitOptions &
- PlatformUtilsServiceInitOptions;
+ PlatformUtilsServiceInitOptions &
+ GlobalStateProvider;
export async function twoFactorServiceFactory(
cache: { twoFactorService?: AbstractTwoFactorService } & CachedServices,
@@ -33,6 +36,7 @@ export async function twoFactorServiceFactory(
new TwoFactorService(
await i18nServiceFactory(cache, opts),
await platformUtilsServiceFactory(cache, opts),
+ await globalStateProviderFactory(cache, opts),
),
);
service.init();
diff --git a/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts b/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts
index a73ec3e1f67..e5a3b8f8f54 100644
--- a/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts
+++ b/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts
@@ -110,7 +110,7 @@ export class AccountSwitcherService {
}),
);
- // Create a reusable observable that listens to the the switchAccountFinish message and returns the userId from the message
+ // Create a reusable observable that listens to the switchAccountFinish message and returns the userId from the message
this.switchAccountFinished$ = fromChromeEvent<[message: { command: string; userId: string }]>(
chrome.runtime.onMessage,
).pipe(
diff --git a/apps/browser/src/auth/popup/two-factor-options.component.ts b/apps/browser/src/auth/popup/two-factor-options.component.ts
index bad2e4a9e77..6191d277add 100644
--- a/apps/browser/src/auth/popup/two-factor-options.component.ts
+++ b/apps/browser/src/auth/popup/two-factor-options.component.ts
@@ -2,7 +2,10 @@ import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { TwoFactorOptionsComponent as BaseTwoFactorOptionsComponent } from "@bitwarden/angular/auth/components/two-factor-options.component";
-import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
+import {
+ TwoFactorProviderDetails,
+ TwoFactorService,
+} from "@bitwarden/common/auth/abstractions/two-factor.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -27,9 +30,9 @@ export class TwoFactorOptionsComponent extends BaseTwoFactorOptionsComponent {
this.navigateTo2FA();
}
- choose(p: any) {
- super.choose(p);
- this.twoFactorService.setSelectedProvider(p.type);
+ override async choose(p: TwoFactorProviderDetails) {
+ await super.choose(p);
+ await this.twoFactorService.setSelectedProvider(p.type);
this.navigateTo2FA();
}
diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts
index 8f85d65692f..10e2d84361b 100644
--- a/apps/browser/src/autofill/services/autofill.service.ts
+++ b/apps/browser/src/autofill/services/autofill.service.ts
@@ -130,7 +130,9 @@ export default class AutofillService implements AutofillServiceInterface {
if (triggeringOnPageLoad && autoFillOnPageLoadIsEnabled) {
injectedScripts.push("autofiller.js");
- } else {
+ }
+
+ if (!triggeringOnPageLoad) {
await this.scriptInjectorService.inject({
tabId: tab.id,
injectDetails: { file: "content/content-message-handler.js", runAt: "document_start" },
diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts
index b4375df7d50..5aec6e01a4b 100644
--- a/apps/browser/src/background/main.background.ts
+++ b/apps/browser/src/background/main.background.ts
@@ -1,10 +1,8 @@
-import { Subject, firstValueFrom, merge } from "rxjs";
+import { Subject, firstValueFrom, merge, timeout } from "rxjs";
import {
PinCryptoServiceAbstraction,
PinCryptoService,
- LoginStrategyServiceAbstraction,
- LoginStrategyService,
InternalUserDecryptionOptionsServiceAbstraction,
UserDecryptionOptionsService,
AuthRequestServiceAbstraction,
@@ -38,7 +36,6 @@ import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarde
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service";
-import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification-api.service.abstraction";
import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
@@ -54,7 +51,6 @@ import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connect
import { MasterPasswordService } from "@bitwarden/common/auth/services/master-password/master-password.service";
import { SsoLoginService } from "@bitwarden/common/auth/services/sso-login.service";
import { TokenService } from "@bitwarden/common/auth/services/token.service";
-import { TwoFactorService } from "@bitwarden/common/auth/services/two-factor.service";
import { UserVerificationApiService } from "@bitwarden/common/auth/services/user-verification/user-verification-api.service";
import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service";
import {
@@ -75,6 +71,7 @@ import {
} from "@bitwarden/common/autofill/services/user-notification-settings.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service";
+import { ClientType } from "@bitwarden/common/enums";
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service";
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
@@ -277,7 +274,6 @@ export default class MainBackground {
containerService: ContainerService;
auditService: AuditServiceAbstraction;
authService: AuthServiceAbstraction;
- loginStrategyService: LoginStrategyServiceAbstraction;
loginEmailService: LoginEmailServiceAbstraction;
importApiService: ImportApiServiceAbstraction;
importService: ImportServiceAbstraction;
@@ -301,7 +297,6 @@ export default class MainBackground {
providerService: ProviderServiceAbstraction;
keyConnectorService: KeyConnectorServiceAbstraction;
userVerificationService: UserVerificationServiceAbstraction;
- twoFactorService: TwoFactorServiceAbstraction;
vaultFilterService: VaultFilterService;
usernameGenerationService: UsernameGenerationServiceAbstraction;
encryptService: EncryptService;
@@ -496,7 +491,7 @@ export default class MainBackground {
this.accountService,
this.singleUserStateProvider,
);
- this.derivedStateProvider = new BackgroundDerivedStateProvider(storageServiceProvider);
+ this.derivedStateProvider = new BackgroundDerivedStateProvider();
this.stateProvider = new DefaultStateProvider(
this.activeUserStateProvider,
this.singleUserStateProvider,
@@ -526,6 +521,7 @@ export default class MainBackground {
this.storageService,
this.logService,
new MigrationBuilderService(),
+ ClientType.Browser,
);
this.stateService = new DefaultBrowserStateService(
@@ -614,8 +610,6 @@ export default class MainBackground {
this.stateService,
);
- this.twoFactorService = new TwoFactorService(this.i18nService, this.platformUtilsService);
-
this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider);
this.devicesApiService = new DevicesApiServiceImplementation(this.apiService);
@@ -659,32 +653,6 @@ export default class MainBackground {
this.loginEmailService = new LoginEmailService(this.stateProvider);
- this.loginStrategyService = new LoginStrategyService(
- this.accountService,
- this.masterPasswordService,
- this.cryptoService,
- this.apiService,
- this.tokenService,
- this.appIdService,
- this.platformUtilsService,
- this.messagingService,
- this.logService,
- this.keyConnectorService,
- this.environmentService,
- this.stateService,
- this.twoFactorService,
- this.i18nService,
- this.encryptService,
- this.passwordStrengthService,
- this.policyService,
- this.deviceTrustService,
- this.authRequestService,
- this.userDecryptionOptionsService,
- this.globalStateProvider,
- this.billingAccountProfileStateService,
- this.kdfConfigService,
- );
-
this.ssoLoginService = new SsoLoginService(this.stateProvider);
this.userVerificationApiService = new UserVerificationApiService(this.apiService);
@@ -1114,8 +1082,7 @@ export default class MainBackground {
this.userKeyInitService.listenForActiveUserChangesToSetUserKey();
await (this.i18nService as I18nService).init();
- await (this.eventUploadService as EventUploadService).init(true);
- this.twoFactorService.init();
+ (this.eventUploadService as EventUploadService).init(true);
if (this.popupOnlyContext) {
return;
@@ -1231,7 +1198,18 @@ export default class MainBackground {
}
async logout(expired: boolean, userId?: UserId) {
- userId ??= (await firstValueFrom(this.accountService.activeAccount$))?.id;
+ userId ??= (
+ await firstValueFrom(
+ this.accountService.activeAccount$.pipe(
+ timeout({
+ first: 2000,
+ with: () => {
+ throw new Error("No active account found to logout");
+ },
+ }),
+ ),
+ )
+ )?.id;
await this.eventUploadService.uploadEvents(userId as UserId);
diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts
index 294346fe9f9..14eb228fb0a 100644
--- a/apps/browser/src/background/runtime.background.ts
+++ b/apps/browser/src/background/runtime.background.ts
@@ -85,7 +85,11 @@ export default class RuntimeBackground {
this.messageListener.allMessages$
.pipe(
mergeMap(async (message: any) => {
- await this.processMessage(message);
+ try {
+ await this.processMessage(message);
+ } catch (err) {
+ this.logService.error(err);
+ }
}),
)
.subscribe();
diff --git a/apps/browser/src/platform/background/service-factories/derived-state-provider.factory.ts b/apps/browser/src/platform/background/service-factories/derived-state-provider.factory.ts
index 4025d01950f..3c3900144bb 100644
--- a/apps/browser/src/platform/background/service-factories/derived-state-provider.factory.ts
+++ b/apps/browser/src/platform/background/service-factories/derived-state-provider.factory.ts
@@ -3,15 +3,10 @@ import { DerivedStateProvider } from "@bitwarden/common/platform/state";
import { BackgroundDerivedStateProvider } from "../../state/background-derived-state.provider";
import { CachedServices, FactoryOptions, factory } from "./factory-options";
-import {
- StorageServiceProviderInitOptions,
- storageServiceProviderFactory,
-} from "./storage-service-provider.factory";
type DerivedStateProviderFactoryOptions = FactoryOptions;
-export type DerivedStateProviderInitOptions = DerivedStateProviderFactoryOptions &
- StorageServiceProviderInitOptions;
+export type DerivedStateProviderInitOptions = DerivedStateProviderFactoryOptions;
export async function derivedStateProviderFactory(
cache: { derivedStateProvider?: DerivedStateProvider } & CachedServices,
@@ -21,7 +16,6 @@ export async function derivedStateProviderFactory(
cache,
"derivedStateProvider",
opts,
- async () =>
- new BackgroundDerivedStateProvider(await storageServiceProviderFactory(cache, opts)),
+ async () => new BackgroundDerivedStateProvider(),
);
}
diff --git a/apps/browser/src/platform/background/service-factories/migration-runner.factory.ts b/apps/browser/src/platform/background/service-factories/migration-runner.factory.ts
index a49699a6158..090531f7cfc 100644
--- a/apps/browser/src/platform/background/service-factories/migration-runner.factory.ts
+++ b/apps/browser/src/platform/background/service-factories/migration-runner.factory.ts
@@ -1,3 +1,4 @@
+import { ClientType } from "@bitwarden/common/enums";
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
@@ -27,6 +28,7 @@ export async function migrationRunnerFactory(
await diskStorageServiceFactory(cache, opts),
await logServiceFactory(cache, opts),
new MigrationBuilderService(),
+ ClientType.Browser,
),
);
}
diff --git a/apps/browser/src/platform/browser/browser-api.ts b/apps/browser/src/platform/browser/browser-api.ts
index b793777d8b6..f536eb8312d 100644
--- a/apps/browser/src/platform/browser/browser-api.ts
+++ b/apps/browser/src/platform/browser/browser-api.ts
@@ -238,10 +238,6 @@ export class BrowserApi {
return typeof window !== "undefined" && window === BrowserApi.getBackgroundPage();
}
- static getApplicationVersion(): string {
- return chrome.runtime.getManifest().version;
- }
-
/**
* Gets the extension views that match the given properties. This method is not
* available within background service worker. As a result, it will return an
diff --git a/apps/browser/src/platform/popup/browser-popup-utils.spec.ts b/apps/browser/src/platform/popup/browser-popup-utils.spec.ts
index e84cd19a45f..c2d33369bd6 100644
--- a/apps/browser/src/platform/popup/browser-popup-utils.spec.ts
+++ b/apps/browser/src/platform/popup/browser-popup-utils.spec.ts
@@ -203,7 +203,7 @@ describe("BrowserPopupUtils", () => {
expect(BrowserPopupUtils["buildPopoutUrl"]).not.toHaveBeenCalled();
});
- it("replaces any existing `uilocation=` query params within the passed extension url path to state the the uilocaiton is a popup", async () => {
+ it("replaces any existing `uilocation=` query params within the passed extension url path to state the uilocation is a popup", async () => {
const url = "popup/index.html?uilocation=sidebar#/tabs/vault";
jest.spyOn(BrowserPopupUtils as any, "isSingleActionPopoutOpen").mockResolvedValueOnce(false);
diff --git a/apps/browser/src/platform/popup/layout/popup-footer.component.html b/apps/browser/src/platform/popup/layout/popup-footer.component.html
new file mode 100644
index 00000000000..2cbbca79c0b
--- /dev/null
+++ b/apps/browser/src/platform/popup/layout/popup-footer.component.html
@@ -0,0 +1,9 @@
+
diff --git a/apps/browser/src/platform/popup/layout/popup-footer.component.ts b/apps/browser/src/platform/popup/layout/popup-footer.component.ts
new file mode 100644
index 00000000000..826a1d1c601
--- /dev/null
+++ b/apps/browser/src/platform/popup/layout/popup-footer.component.ts
@@ -0,0 +1,9 @@
+import { Component } from "@angular/core";
+
+@Component({
+ selector: "popup-footer",
+ templateUrl: "popup-footer.component.html",
+ standalone: true,
+ imports: [],
+})
+export class PopupFooterComponent {}
diff --git a/apps/browser/src/platform/popup/layout/popup-header.component.html b/apps/browser/src/platform/popup/layout/popup-header.component.html
new file mode 100644
index 00000000000..c0894f8168b
--- /dev/null
+++ b/apps/browser/src/platform/popup/layout/popup-header.component.html
@@ -0,0 +1,19 @@
+
+
+
+
+
{{ pageTitle }}
+
+
+
+
+
+
diff --git a/apps/browser/src/platform/popup/layout/popup-header.component.ts b/apps/browser/src/platform/popup/layout/popup-header.component.ts
new file mode 100644
index 00000000000..f2f8eb95af0
--- /dev/null
+++ b/apps/browser/src/platform/popup/layout/popup-header.component.ts
@@ -0,0 +1,34 @@
+import { BooleanInput, coerceBooleanProperty } from "@angular/cdk/coercion";
+import { CommonModule, Location } from "@angular/common";
+import { Component, Input } from "@angular/core";
+
+import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { IconButtonModule, TypographyModule } from "@bitwarden/components";
+
+@Component({
+ selector: "popup-header",
+ templateUrl: "popup-header.component.html",
+ standalone: true,
+ imports: [TypographyModule, CommonModule, IconButtonModule, JslibModule],
+})
+export class PopupHeaderComponent {
+ /** Display the back button, which uses Location.back() to go back one page in history */
+ @Input()
+ get showBackButton() {
+ return this._showBackButton;
+ }
+ set showBackButton(value: BooleanInput) {
+ this._showBackButton = coerceBooleanProperty(value);
+ }
+
+ private _showBackButton = false;
+
+ /** Title string that will be inserted as an h1 */
+ @Input({ required: true }) pageTitle: string;
+
+ constructor(private location: Location) {}
+
+ back() {
+ this.location.back();
+ }
+}
diff --git a/apps/browser/src/platform/popup/layout/popup-layout.mdx b/apps/browser/src/platform/popup/layout/popup-layout.mdx
new file mode 100644
index 00000000000..91f7dab277e
--- /dev/null
+++ b/apps/browser/src/platform/popup/layout/popup-layout.mdx
@@ -0,0 +1,138 @@
+import { Meta, Story, Canvas } from "@storybook/addon-docs";
+
+import * as stories from "./popup-layout.stories";
+
+
+
+Please note that because these stories use `router-outlet`, there are issues with rendering content
+when Light & Dark mode is selected. The stories are best viewed by selecting one color mode.
+
+# Popup Tab Navigation
+
+The popup tab navigation component composes together the popup page and the bottom tab navigation
+footer. This component is intended to be used a level _above_ each extension tab's page code.
+
+The navigation footer contains the 4 main page links for the browser extension. It uses the Angular
+router to determine which page is currently active, and style the button appropriately. Clicking on
+the buttons will navigate to the correct route. The navigation footer has a max-width built in so
+that the page looks nice when the extension is popped out.
+
+Long button names will be ellipsed.
+
+Usage example:
+
+```html
+
+
+
+```
+
+# Popup Page
+
+The popup page handles positioning a page's `header` and `footer` elements, and inserting the rest
+of the content into the `main` element with scroll. There is also a max-width built in so that the
+page looks nice when the extension is popped out.
+
+**Slots**
+
+- `header`
+ - Use `popup-header` component.
+ - Every page should have a header.
+- `footer`
+ - Use the `popup-footer` component.
+ - Not every page will have a footer.
+- default
+ - Whatever content you want in `main`.
+
+Basic usage example:
+
+```html
+
+
+
This is content
+
+
+```
+
+## Popup header
+
+**Args**
+
+- `pageTitle`: required
+ - Inserts title as an `h1`.
+- `showBackButton`: optional, defaults to `false`
+ - Toggles the back button to appear. The back button uses `Location.back()` to navigate back one
+ page in history.
+
+**Slots**
+
+- `end`
+ - Use to insert one or more interactive elements.
+ - The header handles the spacing between elements passed to the `end` slot.
+
+Usage example:
+
+```html
+
+
+
+
+
+
+```
+
+Common interactive elements to insert into the `end` slot are:
+
+- `app-current-account`: shows current account and switcher
+- `app-pop-out`: shows popout button when the extension is not already popped out
+- "Add" button: this can be accomplished with the Button component and any custom functionality for
+ that particular page
+
+## Popup footer
+
+Popup footer should be used when the page displays action buttons. It functions similarly to the
+Dialog footer in that the calling code is responsible for passing in the different buttons that need
+to be rendered.
+
+Usage example:
+
+```html
+
+
+
+
+```
+
+# Page types
+
+There are a few types of pages that are used in the browser extension.
+
+View the story source code to see examples of how to construct these types of pages.
+
+## Extension Tab
+
+Example of wrapping an extension page in the `popup-tab-navigation` component.
+
+
+
+## Extension Page
+
+Examples of using just the `popup-page` component, without and with a footer.
+
+
+
+
+
+## Popped out
+
+When the browser extension is popped out, the "popout" button should not be passed to the header.
+
+
diff --git a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts
new file mode 100644
index 00000000000..1b10e50c0c2
--- /dev/null
+++ b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts
@@ -0,0 +1,367 @@
+import { CommonModule } from "@angular/common";
+import { Component, importProvidersFrom } from "@angular/core";
+import { RouterModule } from "@angular/router";
+import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular";
+
+import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
+import {
+ AvatarModule,
+ ButtonModule,
+ I18nMockService,
+ IconButtonModule,
+} from "@bitwarden/components";
+
+import { PopupFooterComponent } from "./popup-footer.component";
+import { PopupHeaderComponent } from "./popup-header.component";
+import { PopupPageComponent } from "./popup-page.component";
+import { PopupTabNavigationComponent } from "./popup-tab-navigation.component";
+
+@Component({
+ selector: "extension-container",
+ template: `
+