From e8c4c570e929370d73e790599cfebb69003f72f8 Mon Sep 17 00:00:00 2001 From: Addison Beck Date: Mon, 17 Mar 2025 14:23:40 -0400 Subject: [PATCH] fix: save indication of browser installation to storage (#13743) --- .../browser/src/background/main.background.ts | 5 +++ .../src/background/runtime.background.ts | 8 ++++- .../browser-initial-install.service.ts | 31 +++++++++++++++++++ .../src/platform/state/state-definitions.ts | 4 +++ 4 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 apps/browser/src/platform/services/browser-initial-install.service.ts diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 15af1a9bede..f8f86e6a277 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -267,6 +267,7 @@ import { OffscreenDocumentService } from "../platform/offscreen-document/abstrac import { DefaultOffscreenDocumentService } from "../platform/offscreen-document/offscreen-document.service"; import { BrowserTaskSchedulerService } from "../platform/services/abstractions/browser-task-scheduler.service"; import { BrowserEnvironmentService } from "../platform/services/browser-environment.service"; +import BrowserInitialInstallService from "../platform/services/browser-initial-install.service"; import BrowserLocalStorageService from "../platform/services/browser-local-storage.service"; import BrowserMemoryStorageService from "../platform/services/browser-memory-storage.service"; import { BrowserScriptInjectorService } from "../platform/services/browser-script-injector.service"; @@ -390,6 +391,7 @@ export default class MainBackground { kdfConfigService: KdfConfigService; offscreenDocumentService: OffscreenDocumentService; syncServiceListener: SyncServiceListener; + browserInitialInstallService: BrowserInitialInstallService; webPushConnectionService: WorkerWebPushConnectionService | UnsupportedWebPushConnectionService; themeStateService: DefaultThemeStateService; @@ -1043,6 +1045,8 @@ export default class MainBackground { this.organizationVaultExportService, ); + this.browserInitialInstallService = new BrowserInitialInstallService(this.stateProvider); + if (BrowserApi.isManifestVersion(3)) { const registration = (self as unknown as { registration: ServiceWorkerRegistration }) ?.registration; @@ -1146,6 +1150,7 @@ export default class MainBackground { this.accountService, lockService, this.billingAccountProfileStateService, + this.browserInitialInstallService, ); this.nativeMessagingBackground = new NativeMessagingBackground( this.keyService, diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index 32e7432174a..7db72f38139 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -28,6 +28,7 @@ import { LockedVaultPendingNotificationsData } from "../autofill/background/abst import { AutofillService } from "../autofill/services/abstractions/autofill.service"; import { BrowserApi } from "../platform/browser/browser-api"; import { BrowserEnvironmentService } from "../platform/services/browser-environment.service"; +import BrowserInitialInstallService from "../platform/services/browser-initial-install.service"; import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service"; import MainBackground from "./main.background"; @@ -53,6 +54,7 @@ export default class RuntimeBackground { private accountService: AccountService, private readonly lockService: LockService, private billingAccountProfileStateService: BillingAccountProfileStateService, + private browserInitialInstallService: BrowserInitialInstallService, ) { // onInstalled listener must be wired up before anything else, so we do it in the ctor chrome.runtime.onInstalled.addListener((details: any) => { @@ -382,7 +384,10 @@ export default class RuntimeBackground { void this.autofillService.loadAutofillScriptsOnInstall(); if (this.onInstalledReason != null) { - if (this.onInstalledReason === "install") { + if ( + this.onInstalledReason === "install" && + !(await firstValueFrom(this.browserInitialInstallService.extensionInstalled$)) + ) { if (!devFlagEnabled("skipWelcomeOnInstall")) { void BrowserApi.createNewTab("https://bitwarden.com/browser-start/"); } @@ -394,6 +399,7 @@ export default class RuntimeBackground { if (await this.environmentService.hasManagedEnvironment()) { await this.environmentService.setUrlsToManagedEnvironment(); } + await this.browserInitialInstallService.setExtensionInstalled(true); } this.onInstalledReason = null; diff --git a/apps/browser/src/platform/services/browser-initial-install.service.ts b/apps/browser/src/platform/services/browser-initial-install.service.ts new file mode 100644 index 00000000000..12b2ea95b9c --- /dev/null +++ b/apps/browser/src/platform/services/browser-initial-install.service.ts @@ -0,0 +1,31 @@ +import { Observable, map } from "rxjs"; + +import { + GlobalState, + EXTENSION_INITIAL_INSTALL_DISK, + KeyDefinition, + StateProvider, +} from "@bitwarden/common/platform/state"; + +const EXTENSION_INSTALLED = new KeyDefinition( + EXTENSION_INITIAL_INSTALL_DISK, + "extensionInstalled", + { + deserializer: (obj) => obj, + }, +); + +export default class BrowserInitialInstallService { + private extensionInstalled: GlobalState = + this.stateProvider.getGlobal(EXTENSION_INSTALLED); + + readonly extensionInstalled$: Observable = this.extensionInstalled.state$.pipe( + map((x) => x ?? false), + ); + + constructor(private stateProvider: StateProvider) {} + + async setExtensionInstalled(value: boolean) { + await this.extensionInstalled.update(() => value); + } +} diff --git a/libs/common/src/platform/state/state-definitions.ts b/libs/common/src/platform/state/state-definitions.ts index 82cb8bb1e37..b37aeb442ed 100644 --- a/libs/common/src/platform/state/state-definitions.ts +++ b/libs/common/src/platform/state/state-definitions.ts @@ -131,6 +131,10 @@ export const THEMING_DISK = new StateDefinition("theming", "disk", { web: "disk- export const TRANSLATION_DISK = new StateDefinition("translation", "disk", { web: "disk-local" }); export const ANIMATION_DISK = new StateDefinition("animation", "disk"); export const TASK_SCHEDULER_DISK = new StateDefinition("taskScheduler", "disk"); +export const EXTENSION_INITIAL_INSTALL_DISK = new StateDefinition( + "extensionInitialInstall", + "disk", +); // Design System