mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
[PM-19479] Client-Managed SDK state definition (#14839)
* [PM-19479] Client-Managed SDK state definition * Remove test code * Update based on latest sdk * Add DB config * Remove uuid conversion step * Move mapper into separate file * Revert to client managed state * Move mapper to Cipher * Typo --------- Co-authored-by: Andreas Coroiu <andreas.coroiu@gmail.com>
This commit is contained in:
@@ -1467,6 +1467,7 @@ const safeProviders: SafeProvider[] = [
|
||||
AccountServiceAbstraction,
|
||||
KdfConfigService,
|
||||
KeyService,
|
||||
StateProvider,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
|
||||
@@ -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<void> {
|
||||
await stateClient.register_cipher_repository(
|
||||
new RepositoryRecord(userId, stateProvider, new CipherRecordMapper()),
|
||||
);
|
||||
}
|
||||
|
||||
export interface SdkRecordMapper<ClientType, SdkType> {
|
||||
userKeyDefinition(): UserKeyDefinition<Record<string, ClientType>>;
|
||||
toSdk(value: ClientType): SdkType;
|
||||
fromSdk(value: SdkType): ClientType;
|
||||
}
|
||||
|
||||
class RepositoryRecord<ClientType, SdkType> implements Repository<SdkType> {
|
||||
constructor(
|
||||
private userId: UserId,
|
||||
private stateProvider: StateProvider,
|
||||
private mapper: SdkRecordMapper<ClientType, SdkType>,
|
||||
) {}
|
||||
|
||||
async get(id: string): Promise<SdkType | null> {
|
||||
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<SdkType[]> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
22
libs/common/src/vault/models/domain/cipher-sdk-mapper.ts
Normal file
22
libs/common/src/vault/models/domain/cipher-sdk-mapper.ts
Normal file
@@ -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<CipherData, SdkCipher> {
|
||||
userKeyDefinition(): UserKeyDefinition<Record<string, CipherData>> {
|
||||
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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user