From 6b30234614e265db87bf0354d3afe4820be33d0c Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 19 Jan 2026 16:14:36 +0100 Subject: [PATCH] feat: use new service as the init trigger --- apps/desktop/src/app/services/init.service.ts | 77 ++++++++++--------- .../src/app/services/services.module.ts | 17 ++-- .../decentralized-init.service.ts | 15 ++++ 3 files changed, 64 insertions(+), 45 deletions(-) diff --git a/apps/desktop/src/app/services/init.service.ts b/apps/desktop/src/app/services/init.service.ts index 17115825bf6..5fb1133258a 100644 --- a/apps/desktop/src/app/services/init.service.ts +++ b/apps/desktop/src/app/services/init.service.ts @@ -1,6 +1,7 @@ -import { Inject, Injectable, DOCUMENT } from "@angular/core"; +import { Inject, Injectable, DOCUMENT, Type } from "@angular/core"; import { firstValueFrom } from "rxjs"; +import { Initializable } from "@bitwarden/angular/platform/abstractions/decentralized-init.service"; import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service"; @@ -31,7 +32,7 @@ import { BiometricMessageHandlerService } from "../../services/biometric-message import { NativeMessagingService } from "../../services/native-messaging.service"; @Injectable() -export class InitService { +export class InitService implements Initializable { constructor( @Inject(WINDOW) private win: Window, private syncService: SyncServiceAbstraction, @@ -59,46 +60,46 @@ export class InitService { private readonly migrationRunner: MigrationRunner, ) {} - init() { - return async () => { - await this.sdkLoadService.loadAndInit(); - await this.sshAgentService.init(); - this.nativeMessagingService.init(); - await this.migrationRunner.waitForCompletion(); // Desktop will run migrations in the main process - this.encryptService.init(this.configService); + dependencies: Type[] = []; - const accounts = await firstValueFrom(this.accountService.accounts$); - const setUserKeyInMemoryPromises = []; - for (const userId of Object.keys(accounts) as UserId[]) { - // For each acct, we must await the process of setting the user key in memory - // if the auto user key is set to avoid race conditions of any code trying to access - // the user key from mem. - setUserKeyInMemoryPromises.push( - this.userAutoUnlockKeyService.setUserKeyInMemoryIfAutoUserKeySet(userId), - ); - } - await Promise.all(setUserKeyInMemoryPromises); + async init() { + await this.sdkLoadService.loadAndInit(); + await this.sshAgentService.init(); + this.nativeMessagingService.init(); + await this.migrationRunner.waitForCompletion(); // Desktop will run migrations in the main process + this.encryptService.init(this.configService); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.syncService.fullSync(true); - await this.vaultTimeoutService.init(true); - await (this.i18nService as I18nRendererService).init(); - (this.eventUploadService as EventUploadService).init(true); - this.twoFactorService.init(); - this.notificationsService.startListening(); - const htmlEl = this.win.document.documentElement; - htmlEl.classList.add("os_" + this.platformUtilsService.getDeviceString()); - this.themingService.applyThemeChangesTo(this.document); + const accounts = await firstValueFrom(this.accountService.accounts$); + const setUserKeyInMemoryPromises = []; + for (const userId of Object.keys(accounts) as UserId[]) { + // For each acct, we must await the process of setting the user key in memory + // if the auto user key is set to avoid race conditions of any code trying to access + // the user key from mem. + setUserKeyInMemoryPromises.push( + this.userAutoUnlockKeyService.setUserKeyInMemoryIfAutoUserKeySet(userId), + ); + } + await Promise.all(setUserKeyInMemoryPromises); - this.versionService.init(); + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.syncService.fullSync(true); + await this.vaultTimeoutService.init(true); + await (this.i18nService as I18nRendererService).init(); + (this.eventUploadService as EventUploadService).init(true); + this.twoFactorService.init(); + this.notificationsService.startListening(); + const htmlEl = this.win.document.documentElement; + htmlEl.classList.add("os_" + this.platformUtilsService.getDeviceString()); + this.themingService.applyThemeChangesTo(this.document); - const containerService = new ContainerService(this.keyService, this.encryptService); - containerService.attachToGlobal(this.win); + this.versionService.init(); - await this.biometricMessageHandlerService.init(); - await this.autofillService.init(); - await this.autotypeService.init(); - }; + const containerService = new ContainerService(this.keyService, this.encryptService); + containerService.attachToGlobal(this.win); + + await this.biometricMessageHandlerService.init(); + await this.autofillService.init(); + await this.autotypeService.init(); } } diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index a5a91c52e7e..66ba390aeee 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -1,11 +1,15 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { APP_INITIALIZER, NgModule } from "@angular/core"; +import { inject, NgModule, provideAppInitializer } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; import { Subject, merge } from "rxjs"; import { CollectionService, OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { SetInitialPasswordService } from "@bitwarden/angular/auth/password-management/set-initial-password/set-initial-password.service.abstraction"; +import { + DecentralizedInitService, + initializableProvider, +} from "@bitwarden/angular/platform/abstractions/decentralized-init.service"; import { SafeProvider, safeProvider } from "@bitwarden/angular/platform/utils/safe-provider"; import { SECURE_STORAGE, @@ -194,12 +198,10 @@ const safeProviders: SafeProvider[] = [ safeProvider(BiometricMessageHandlerService), safeProvider(SearchBarService), safeProvider(DialogService), - safeProvider({ - provide: APP_INITIALIZER as SafeInjectionToken<() => void>, - useFactory: (initService: InitService) => initService.init(), - deps: [InitService], - multi: true, - }), + provideAppInitializer(() => { + const initService = inject(DecentralizedInitService); + return initService.init(); + }) as any, safeProvider({ provide: RELOAD_CALLBACK, useValue: null, @@ -559,6 +561,7 @@ const safeProviders: SafeProvider[] = [ LogService, ], }), + initializableProvider(InitService), ]; @NgModule({ diff --git a/libs/angular/src/platform/abstractions/decentralized-init.service.ts b/libs/angular/src/platform/abstractions/decentralized-init.service.ts index d2517200b87..ecb2b174491 100644 --- a/libs/angular/src/platform/abstractions/decentralized-init.service.ts +++ b/libs/angular/src/platform/abstractions/decentralized-init.service.ts @@ -1,5 +1,7 @@ import { InjectionToken, Type } from "@angular/core"; +import { SafeProvider } from "../utils/safe-provider"; + /** * Services that implement Initializable can participate in decentralized initialization. * Each service declares its dependencies, and the DecentralizedInitService will execute @@ -33,6 +35,19 @@ export abstract class Initializable { */ export const INIT_SERVICES = new InjectionToken("INIT_SERVICES"); +/** + * Helper function to create a type-safe provider for an Initializable service. + * + * @param type The Initializable service class + */ +export function initializableProvider>(ctor: T) { + return { + provide: INIT_SERVICES, + useExisting: ctor, + multi: true, + } as SafeProvider; +} + /** * Service responsible for coordinating decentralized initialization. * Discovers all registered Initializable services and executes their init()