mirror of
https://github.com/bitwarden/browser
synced 2026-01-27 23:03:45 +00:00
feat-wip: migrate sdk and ssh services
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { Inject, Injectable, DOCUMENT, Type } from "@angular/core";
|
||||
import { Inject, Injectable, DOCUMENT } 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";
|
||||
@@ -11,9 +10,9 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract
|
||||
import { DefaultVaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { Dependency, Initializable } from "@bitwarden/common/platform/abstractions/initializable";
|
||||
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
|
||||
import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { ServerNotificationsService } from "@bitwarden/common/platform/server-notifications";
|
||||
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
|
||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||
@@ -42,30 +41,23 @@ export class InitService implements Initializable {
|
||||
private twoFactorService: TwoFactorService,
|
||||
private notificationsService: ServerNotificationsService,
|
||||
private platformUtilsService: PlatformUtilsServiceAbstraction,
|
||||
private stateService: StateServiceAbstraction,
|
||||
private keyService: KeyServiceAbstraction,
|
||||
private nativeMessagingService: NativeMessagingService,
|
||||
private themingService: AbstractThemingService,
|
||||
private encryptService: EncryptService,
|
||||
private userAutoUnlockKeyService: UserAutoUnlockKeyService,
|
||||
private accountService: AccountService,
|
||||
private versionService: VersionService,
|
||||
private sshAgentService: SshAgentService,
|
||||
private autofillService: DesktopAutofillService,
|
||||
private autotypeService: DesktopAutotypeService,
|
||||
private sdkLoadService: SdkLoadService,
|
||||
private biometricMessageHandlerService: BiometricMessageHandlerService,
|
||||
private configService: ConfigService,
|
||||
@Inject(DOCUMENT) private document: Document,
|
||||
private readonly migrationRunner: MigrationRunner,
|
||||
) {}
|
||||
|
||||
dependencies: Type<Initializable>[] = [];
|
||||
dependencies: Dependency[] = [SdkLoadService, SshAgentService, NativeMessagingService];
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@@ -146,6 +146,7 @@ import { DesktopAutofillService } from "../../autofill/services/desktop-autofill
|
||||
import { DesktopAutotypeDefaultSettingPolicy } from "../../autofill/services/desktop-autotype-policy.service";
|
||||
import { DesktopAutotypeService } from "../../autofill/services/desktop-autotype.service";
|
||||
import { DesktopFido2UserInterfaceService } from "../../autofill/services/desktop-fido2-user-interface.service";
|
||||
import { SshAgentService } from "../../autofill/services/ssh-agent.service";
|
||||
import { DesktopBiometricsService } from "../../key-management/biometrics/desktop.biometrics.service";
|
||||
import { RendererBiometricsService } from "../../key-management/biometrics/renderer-biometrics.service";
|
||||
import { ElectronKeyService } from "../../key-management/electron-key.service";
|
||||
@@ -562,6 +563,9 @@ const safeProviders: SafeProvider[] = [
|
||||
],
|
||||
}),
|
||||
initializableProvider(InitService),
|
||||
initializableProvider(SdkLoadService),
|
||||
// initializableProvider(flagEnabled("sdk") ? DefaultSdkLoadService : NoopSdkLoadService),
|
||||
initializableProvider(SshAgentService),
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
||||
@@ -25,6 +25,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { Initializable } from "@bitwarden/common/platform/abstractions/initializable";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { CommandDefinition, MessageListener } from "@bitwarden/common/platform/messaging";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
@@ -39,7 +40,7 @@ import { SshAgentPromptType } from "../models/ssh-agent-setting";
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
})
|
||||
export class SshAgentService implements OnDestroy {
|
||||
export class SshAgentService implements OnDestroy, Initializable {
|
||||
SSH_REFRESH_INTERVAL = 1000;
|
||||
SSH_VAULT_UNLOCK_REQUEST_TIMEOUT = 60_000;
|
||||
SSH_REQUEST_UNLOCK_POLLING_INTERVAL = 100;
|
||||
|
||||
@@ -45,11 +45,11 @@ import { SearchCiphersPipe } from "./pipes/search-ciphers.pipe";
|
||||
import { SearchPipe } from "./pipes/search.pipe";
|
||||
import { UserNamePipe } from "./pipes/user-name.pipe";
|
||||
import { UserTypePipe } from "./pipes/user-type.pipe";
|
||||
import { DecentralizedInitService as DecentralizedInitServiceAbstraction } from "./platform/abstractions/decentralized-init.service";
|
||||
import { DecentralizedInitService } from "./platform/abstractions/decentralized-init.service";
|
||||
import { EllipsisPipe } from "./platform/pipes/ellipsis.pipe";
|
||||
import { FingerprintPipe } from "./platform/pipes/fingerprint.pipe";
|
||||
import { I18nPipe } from "./platform/pipes/i18n.pipe";
|
||||
import { DecentralizedInitService } from "./platform/services/decentralized-init.service";
|
||||
import { DefaultDecentralizedInitService } from "./platform/services/default-decentralized-init.service";
|
||||
import { safeProvider } from "./platform/utils/safe-provider";
|
||||
import { IconComponent } from "./vault/components/icon.component";
|
||||
|
||||
@@ -149,8 +149,8 @@ import { IconComponent } from "./vault/components/icon.component";
|
||||
FingerprintPipe,
|
||||
PluralizePipe,
|
||||
safeProvider({
|
||||
provide: DecentralizedInitServiceAbstraction,
|
||||
useClass: DecentralizedInitService,
|
||||
provide: DecentralizedInitService,
|
||||
useClass: DefaultDecentralizedInitService,
|
||||
useAngularDecorators: true,
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -1,26 +1,9 @@
|
||||
import { InjectionToken, Type } from "@angular/core";
|
||||
import { InjectionToken } from "@angular/core";
|
||||
|
||||
import { Dependency, Initializable } from "@bitwarden/common/platform/abstractions/initializable";
|
||||
|
||||
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
|
||||
* them in the correct order using topological sort.
|
||||
*/
|
||||
export abstract class Initializable {
|
||||
/**
|
||||
* List of service classes that must be initialized before this service.
|
||||
* Use actual class references for type safety and refactoring support.
|
||||
*/
|
||||
abstract dependencies: Type<Initializable>[];
|
||||
|
||||
/**
|
||||
* Initialize this service. Called after all dependencies have been initialized.
|
||||
* Can be async or sync.
|
||||
*/
|
||||
abstract init(): Promise<void> | void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multi-provider token for registering services that need initialization.
|
||||
* Services register themselves by adding to their library's provider bundle:
|
||||
@@ -40,7 +23,7 @@ export const INIT_SERVICES = new InjectionToken<Initializable[]>("INIT_SERVICES"
|
||||
*
|
||||
* @param type The Initializable service class
|
||||
*/
|
||||
export function initializableProvider<T extends Type<Initializable>>(ctor: T) {
|
||||
export function initializableProvider<T extends Dependency>(ctor: T) {
|
||||
return {
|
||||
provide: INIT_SERVICES,
|
||||
useExisting: ctor,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* Example usage of DecentralizedInitService
|
||||
* Example usage of DefaultDecentralizedInitService
|
||||
*
|
||||
* This file demonstrates how to:
|
||||
* 1. Make services implement Initializable
|
||||
* 2. Register services with INIT_SERVICES
|
||||
* 3. Use DecentralizedInitService in your app
|
||||
* 3. Use DefaultDecentralizedInitService in your app
|
||||
*
|
||||
* This is NOT production code - it's a reference example.
|
||||
*/
|
||||
@@ -90,17 +90,17 @@ export const EXAMPLE_LIBRARY_PROVIDERS = [
|
||||
/**
|
||||
* In your app's main config (e.g., app.config.ts or main.ts):
|
||||
*
|
||||
* import { DecentralizedInitService } from '@bitwarden/angular/platform/services/decentralized-init.service';
|
||||
* import { DefaultDecentralizedInitService } from '@bitwarden/angular/platform/services/default-decentralized-init.service';
|
||||
* import { EXAMPLE_LIBRARY_PROVIDERS } from '@bitwarden/angular/platform/services/decentralized-init.service.example';
|
||||
*
|
||||
* export const appConfig: ApplicationConfig = {
|
||||
* providers: [
|
||||
* ...EXAMPLE_LIBRARY_PROVIDERS,
|
||||
* DecentralizedInitService,
|
||||
* DefaultDecentralizedInitService,
|
||||
* {
|
||||
* provide: APP_INITIALIZER,
|
||||
* useFactory: (initService: DecentralizedInitService) => () => initService.init(),
|
||||
* deps: [DecentralizedInitService],
|
||||
* useFactory: (initService: DefaultDecentralizedInitService) => () => initService.init(),
|
||||
* deps: [DefaultDecentralizedInitService],
|
||||
* multi: true,
|
||||
* },
|
||||
* ]
|
||||
@@ -110,7 +110,7 @@ export const EXAMPLE_LIBRARY_PROVIDERS = [
|
||||
*
|
||||
* @Component({ ... })
|
||||
* export class AppComponent {
|
||||
* constructor(private initService: DecentralizedInitService) {}
|
||||
* constructor(private initService: DefaultDecentralizedInitService) {}
|
||||
*
|
||||
* ngOnInit() {
|
||||
* await this.initService.init();
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Type } from "@angular/core";
|
||||
|
||||
import { Initializable } from "../abstractions/decentralized-init.service";
|
||||
|
||||
import { DecentralizedInitService } from "./decentralized-init.service";
|
||||
import { DefaultDecentralizedInitService } from "./default-decentralized-init.service";
|
||||
|
||||
// Test service implementations
|
||||
class TestService implements Initializable {
|
||||
@@ -23,7 +23,7 @@ function createTrackingService(name: string, executionOrder: string[]) {
|
||||
};
|
||||
}
|
||||
|
||||
describe("DecentralizedInitService", () => {
|
||||
describe("DefaultDecentralizedInitService", () => {
|
||||
let executionOrder: string[];
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -34,7 +34,7 @@ describe("DecentralizedInitService", () => {
|
||||
describe("given no registered services", () => {
|
||||
it("completes without error when called", async () => {
|
||||
// Arrange
|
||||
const sut = new DecentralizedInitService([]);
|
||||
const sut = new DefaultDecentralizedInitService([]);
|
||||
|
||||
// Act & Assert
|
||||
await expect(sut.init()).resolves.not.toThrow();
|
||||
@@ -45,7 +45,7 @@ describe("DecentralizedInitService", () => {
|
||||
it("initializes a single service when called", async () => {
|
||||
// Arrange
|
||||
const service = new TestService();
|
||||
const sut = new DecentralizedInitService([service]);
|
||||
const sut = new DefaultDecentralizedInitService([service]);
|
||||
|
||||
// Act
|
||||
await sut.init();
|
||||
@@ -59,7 +59,7 @@ describe("DecentralizedInitService", () => {
|
||||
const service1 = new TestService();
|
||||
const service2 = new TestService();
|
||||
const service3 = new TestService();
|
||||
const sut = new DecentralizedInitService([service1, service2, service3]);
|
||||
const sut = new DefaultDecentralizedInitService([service1, service2, service3]);
|
||||
|
||||
// Act
|
||||
await sut.init();
|
||||
@@ -81,7 +81,7 @@ describe("DecentralizedInitService", () => {
|
||||
const serviceB = new ServiceB();
|
||||
serviceB.dependencies = [ServiceA];
|
||||
|
||||
const sut = new DecentralizedInitService([serviceB, serviceA]);
|
||||
const sut = new DefaultDecentralizedInitService([serviceB, serviceA]);
|
||||
|
||||
// Act
|
||||
await sut.init();
|
||||
@@ -107,7 +107,7 @@ describe("DecentralizedInitService", () => {
|
||||
serviceC.dependencies = [ServiceA, ServiceB];
|
||||
serviceD.dependencies = [ServiceC];
|
||||
|
||||
const sut = new DecentralizedInitService([serviceD, serviceB, serviceC, serviceA]);
|
||||
const sut = new DefaultDecentralizedInitService([serviceD, serviceB, serviceC, serviceA]);
|
||||
|
||||
// Act
|
||||
await sut.init();
|
||||
@@ -139,7 +139,7 @@ describe("DecentralizedInitService", () => {
|
||||
serviceC.dependencies = [ServiceA];
|
||||
serviceD.dependencies = [ServiceB, ServiceC];
|
||||
|
||||
const sut = new DecentralizedInitService([serviceD, serviceC, serviceB, serviceA]);
|
||||
const sut = new DefaultDecentralizedInitService([serviceD, serviceC, serviceB, serviceA]);
|
||||
|
||||
// Act
|
||||
await sut.init();
|
||||
@@ -166,7 +166,7 @@ describe("DecentralizedInitService", () => {
|
||||
}
|
||||
|
||||
const service = new CountingService();
|
||||
const sut = new DecentralizedInitService([service]);
|
||||
const sut = new DefaultDecentralizedInitService([service]);
|
||||
|
||||
// Act
|
||||
await sut.init();
|
||||
@@ -188,7 +188,7 @@ describe("DecentralizedInitService", () => {
|
||||
serviceA.dependencies = [ServiceB as Type<Initializable>];
|
||||
serviceB.dependencies = [ServiceA as Type<Initializable>];
|
||||
|
||||
const sut = new DecentralizedInitService([serviceA, serviceB]);
|
||||
const sut = new DefaultDecentralizedInitService([serviceA, serviceB]);
|
||||
|
||||
// Act & Assert
|
||||
await expect(sut.init()).rejects.toThrow(/Circular dependency detected/);
|
||||
@@ -208,7 +208,7 @@ describe("DecentralizedInitService", () => {
|
||||
serviceB.dependencies = [ServiceC as Type<Initializable>];
|
||||
serviceC.dependencies = [ServiceA as Type<Initializable>];
|
||||
|
||||
const sut = new DecentralizedInitService([serviceA, serviceB, serviceC]);
|
||||
const sut = new DefaultDecentralizedInitService([serviceA, serviceB, serviceC]);
|
||||
|
||||
// Act & Assert
|
||||
await expect(sut.init()).rejects.toThrow(/Circular dependency detected/);
|
||||
@@ -224,7 +224,7 @@ describe("DecentralizedInitService", () => {
|
||||
}
|
||||
|
||||
const serviceB = new ServiceB();
|
||||
const sut = new DecentralizedInitService([serviceB]);
|
||||
const sut = new DefaultDecentralizedInitService([serviceB]);
|
||||
|
||||
// Act & Assert
|
||||
await expect(sut.init()).rejects.toThrow(/not registered in INIT_SERVICES/);
|
||||
@@ -238,7 +238,7 @@ describe("DecentralizedInitService", () => {
|
||||
}
|
||||
|
||||
const myService = new MyService();
|
||||
const sut = new DecentralizedInitService([myService]);
|
||||
const sut = new DefaultDecentralizedInitService([myService]);
|
||||
|
||||
// Act & Assert
|
||||
await expect(sut.init()).rejects.toThrow("MyService depends on MyDependency");
|
||||
@@ -256,7 +256,7 @@ describe("DecentralizedInitService", () => {
|
||||
}
|
||||
|
||||
const service = new FailingService();
|
||||
const sut = new DecentralizedInitService([service]);
|
||||
const sut = new DefaultDecentralizedInitService([service]);
|
||||
|
||||
// Act & Assert
|
||||
await expect(sut.init()).rejects.toThrow(/Failed to initialize FailingService/);
|
||||
@@ -275,7 +275,7 @@ describe("DecentralizedInitService", () => {
|
||||
}
|
||||
|
||||
const service = new SyncService();
|
||||
const sut = new DecentralizedInitService([service]);
|
||||
const sut = new DefaultDecentralizedInitService([service]);
|
||||
|
||||
// Act
|
||||
await sut.init();
|
||||
@@ -305,7 +305,7 @@ describe("DecentralizedInitService", () => {
|
||||
|
||||
const syncService = new SyncService();
|
||||
const asyncService = new AsyncService();
|
||||
const sut = new DecentralizedInitService([asyncService, syncService]);
|
||||
const sut = new DefaultDecentralizedInitService([asyncService, syncService]);
|
||||
|
||||
// Act
|
||||
await sut.init();
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Inject, Injectable, Type } from "@angular/core";
|
||||
import { Inject, Injectable } from "@angular/core";
|
||||
|
||||
import { Dependency, Initializable } from "@bitwarden/common/platform/abstractions/initializable";
|
||||
|
||||
import {
|
||||
DecentralizedInitService as DecentralizedInitServiceAbstraction,
|
||||
Initializable,
|
||||
DecentralizedInitService,
|
||||
INIT_SERVICES,
|
||||
} from "../abstractions/decentralized-init.service";
|
||||
|
||||
@@ -18,7 +19,7 @@ import {
|
||||
* - Executes init() methods sequentially in dependency order
|
||||
*/
|
||||
@Injectable()
|
||||
export class DecentralizedInitService implements DecentralizedInitServiceAbstraction {
|
||||
export class DefaultDecentralizedInitService implements DecentralizedInitService {
|
||||
constructor(@Inject(INIT_SERVICES) private initServices: Initializable[]) {}
|
||||
|
||||
async init(): Promise<void> {
|
||||
@@ -46,9 +47,9 @@ export class DecentralizedInitService implements DecentralizedInitServiceAbstrac
|
||||
*/
|
||||
private topologicalSort(services: Initializable[]): Initializable[] {
|
||||
// Build a map from constructor to instance for quick lookup
|
||||
const instanceMap = new Map<Type<Initializable>, Initializable>();
|
||||
const instanceMap = new Map<Dependency, Initializable>();
|
||||
for (const service of services) {
|
||||
instanceMap.set(service.constructor as Type<Initializable>, service);
|
||||
instanceMap.set(service.constructor as Dependency, service);
|
||||
}
|
||||
|
||||
const sorted: Initializable[] = [];
|
||||
@@ -70,7 +71,7 @@ export class DecentralizedInitService implements DecentralizedInitServiceAbstrac
|
||||
const currentPath = [...path, service.constructor.name];
|
||||
|
||||
// Visit all dependencies first
|
||||
for (const depClass of service.dependencies) {
|
||||
for (const depClass of service.dependencies ?? []) {
|
||||
const depInstance = instanceMap.get(depClass);
|
||||
|
||||
if (!depInstance) {
|
||||
29
libs/common/src/platform/abstractions/initializable.ts
Normal file
29
libs/common/src/platform/abstractions/initializable.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
declare const Dependency: FunctionConstructor;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
export interface Dependency extends Function {
|
||||
prototype: Initializable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Services that implement Initializable can participate in decentralized initialization.
|
||||
* Each service declares its dependencies, and initialization will execute them in the
|
||||
* correct order using topological sort.
|
||||
*
|
||||
* This is a framework-agnostic abstraction that can be used across all clients.
|
||||
*/
|
||||
export interface Initializable {
|
||||
/**
|
||||
* List of service classes that must be initialized before this service.
|
||||
* Use actual class references for type safety and refactoring support.
|
||||
*
|
||||
* Note: The exact type depends on the framework. For Angular, use Type<Initializable>.
|
||||
* For non-Angular clients, use the constructor type directly.
|
||||
*/
|
||||
dependencies?: Dependency[];
|
||||
|
||||
/**
|
||||
* Initialize this service. Called after all dependencies have been initialized.
|
||||
* Can be async or sync.
|
||||
*/
|
||||
init(): Promise<void> | void;
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import { init_sdk, LogLevel } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { Initializable } from "../initializable";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- used in docs
|
||||
import type { SdkService } from "./sdk.service";
|
||||
|
||||
@@ -9,7 +11,7 @@ export class SdkLoadFailedError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class SdkLoadService {
|
||||
export abstract class SdkLoadService implements Initializable {
|
||||
protected static logLevel: LogLevel = LogLevel.Info;
|
||||
private static markAsReady: () => void;
|
||||
private static markAsFailed: (error: unknown) => void;
|
||||
@@ -39,7 +41,7 @@ export abstract class SdkLoadService {
|
||||
* This method should be called once at the start of the application.
|
||||
* Raw functions and classes from the SDK can be used after this method resolves.
|
||||
*/
|
||||
async loadAndInit(): Promise<void> {
|
||||
async init(): Promise<void> {
|
||||
try {
|
||||
await this.load();
|
||||
init_sdk(SdkLoadService.logLevel);
|
||||
|
||||
Reference in New Issue
Block a user