diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index a5fb9397125..cd045c9874e 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -779,6 +779,7 @@ export default class MainBackground { this.accountService, this.kdfConfigService, this.keyService, + this.stateProvider, ); this.passwordStrengthService = new PasswordStrengthService(); diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index e82ceb5a6e9..0268eed06ae 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -599,6 +599,7 @@ export class ServiceContainer { this.accountService, this.kdfConfigService, this.keyService, + this.stateProvider, customUserAgent, ); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 50d1c477c96..ab7c12ceffa 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1467,6 +1467,7 @@ const safeProviders: SafeProvider[] = [ AccountServiceAbstraction, KdfConfigService, KeyService, + StateProvider, ], }), safeProvider({ diff --git a/libs/common/src/platform/services/sdk/client-managed-state.ts b/libs/common/src/platform/services/sdk/client-managed-state.ts new file mode 100644 index 00000000000..1e3273d0801 --- /dev/null +++ b/libs/common/src/platform/services/sdk/client-managed-state.ts @@ -0,0 +1,64 @@ +import { firstValueFrom, map } from "rxjs"; + +import { UserId } from "@bitwarden/common/types/guid"; +import { CipherRecordMapper } from "@bitwarden/common/vault/models/domain/cipher-sdk-mapper"; +import { StateClient, Repository } from "@bitwarden/sdk-internal"; + +import { StateProvider, UserKeyDefinition } from "../../state"; + +export async function initializeState( + userId: UserId, + stateClient: StateClient, + stateProvider: StateProvider, +): Promise { + await stateClient.register_cipher_repository( + new RepositoryRecord(userId, stateProvider, new CipherRecordMapper()), + ); +} + +export interface SdkRecordMapper { + userKeyDefinition(): UserKeyDefinition>; + toSdk(value: ClientType): SdkType; + fromSdk(value: SdkType): ClientType; +} + +class RepositoryRecord implements Repository { + constructor( + private userId: UserId, + private stateProvider: StateProvider, + private mapper: SdkRecordMapper, + ) {} + + async get(id: string): Promise { + const prov = this.stateProvider.getUser(this.userId, this.mapper.userKeyDefinition()); + const data = await firstValueFrom(prov.state$.pipe(map((data) => data ?? {}))); + const element = data[id]; + if (!element) { + return null; + } + return this.mapper.toSdk(element); + } + + async list(): Promise { + const prov = this.stateProvider.getUser(this.userId, this.mapper.userKeyDefinition()); + const elements = await firstValueFrom(prov.state$.pipe(map((data) => data ?? {}))); + return Object.values(elements).map((element) => this.mapper.toSdk(element)); + } + + async set(id: string, value: SdkType): Promise { + const prov = this.stateProvider.getUser(this.userId, this.mapper.userKeyDefinition()); + const elements = await firstValueFrom(prov.state$.pipe(map((data) => data ?? {}))); + elements[id] = this.mapper.fromSdk(value); + await prov.update(() => elements); + } + + async remove(id: string): Promise { + const prov = this.stateProvider.getUser(this.userId, this.mapper.userKeyDefinition()); + const elements = await firstValueFrom(prov.state$.pipe(map((data) => data ?? {}))); + if (!elements[id]) { + return; + } + delete elements[id]; + await prov.update(() => elements); + } +} diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index d8780b0f1f4..4359f03a17f 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -38,6 +38,9 @@ import { SdkLoadService } from "../../abstractions/sdk/sdk-load.service"; import { SdkService, UserNotLoggedInError } from "../../abstractions/sdk/sdk.service"; import { compareValues } from "../../misc/compare-values"; import { Rc } from "../../misc/reference-counting/rc"; +import { StateProvider } from "../../state"; + +import { initializeState } from "./client-managed-state"; // A symbol that represents an overriden client that is explicitly set to undefined, // blocking the creation of an internal client for that user. @@ -81,6 +84,7 @@ export class DefaultSdkService implements SdkService { private accountService: AccountService, private kdfConfigService: KdfConfigService, private keyService: KeyService, + private stateProvider?: StateProvider, private userAgent: string | null = null, ) {} @@ -241,6 +245,12 @@ export class DefaultSdkService implements SdkService { .map(([k, v]) => [k, v.key as UnsignedSharedKey]), ), }); + + // This is optional to avoid having to mock it on the tests + if (this.stateProvider) { + // Initialize the SDK managed database and the client managed repositories. + await initializeState(userId, client.platform().state(), this.stateProvider); + } } private toSettings(env: Environment): ClientSettings { diff --git a/libs/common/src/vault/models/domain/cipher-sdk-mapper.ts b/libs/common/src/vault/models/domain/cipher-sdk-mapper.ts new file mode 100644 index 00000000000..644a9ff7645 --- /dev/null +++ b/libs/common/src/vault/models/domain/cipher-sdk-mapper.ts @@ -0,0 +1,22 @@ +import { SdkRecordMapper } from "@bitwarden/common/platform/services/sdk/client-managed-state"; +import { UserKeyDefinition } from "@bitwarden/common/platform/state"; +import { Cipher as SdkCipher } from "@bitwarden/sdk-internal"; + +import { ENCRYPTED_CIPHERS } from "../../services/key-state/ciphers.state"; +import { CipherData } from "../data/cipher.data"; + +import { Cipher } from "./cipher"; + +export class CipherRecordMapper implements SdkRecordMapper { + userKeyDefinition(): UserKeyDefinition> { + return ENCRYPTED_CIPHERS; + } + + toSdk(value: CipherData): SdkCipher { + return new Cipher(value).toSdkCipher(); + } + + fromSdk(value: SdkCipher): CipherData { + throw new Error("Cipher.fromSdk is not implemented yet"); + } +}