mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 15:53:27 +00:00
Improve SDK direct function usage (#13353)
* feat: initalize WASM/SDK directly after load * fix: default sdk service trying to set log level * feat: wait for sdk to load in sdk service * fix: add required disposable polyfills * feat: update sdk version * feat: replace rc-specific workaround with global polyfill * fix: sdk service tests
This commit is contained in:
@@ -1,3 +1,52 @@
|
||||
export abstract class SdkLoadService {
|
||||
abstract load(): Promise<void>;
|
||||
import { init_sdk } from "@bitwarden/sdk-internal";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- used in docs
|
||||
import type { SdkService } from "./sdk.service";
|
||||
|
||||
export class SdkLoadFailedError extends Error {
|
||||
constructor(error: unknown) {
|
||||
super(`SDK loading failed: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class SdkLoadService {
|
||||
private static markAsReady: () => void;
|
||||
private static markAsFailed: (error: unknown) => void;
|
||||
|
||||
/**
|
||||
* This promise is resolved when the SDK is ready to be used. Use it when your code might run early and/or is not able to use DI.
|
||||
* Beware that WASM always requires a load step which makes it tricky to use functions and classes directly, it is therefore recommended
|
||||
* to use the SDK through the {@link SdkService}. Only use this promise in advanced scenarios!
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { pureFunction } from "@bitwarden/sdk-internal";
|
||||
*
|
||||
* async function myFunction() {
|
||||
* await SdkLoadService.Ready;
|
||||
* pureFunction();
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
static readonly Ready = new Promise<void>((resolve, reject) => {
|
||||
SdkLoadService.markAsReady = resolve;
|
||||
SdkLoadService.markAsFailed = (error: unknown) => reject(new SdkLoadFailedError(error));
|
||||
});
|
||||
|
||||
/**
|
||||
* Load WASM and initalize SDK-JS integrations such as logging.
|
||||
* 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> {
|
||||
try {
|
||||
await this.load();
|
||||
init_sdk();
|
||||
SdkLoadService.markAsReady();
|
||||
} catch (error) {
|
||||
SdkLoadService.markAsFailed(error);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract load(): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
// Temporary workaround for Symbol.dispose
|
||||
// remove when https://github.com/jestjs/jest/issues/14874 is resolved and *released*
|
||||
const disposeSymbol: unique symbol = Symbol("Symbol.dispose");
|
||||
const asyncDisposeSymbol: unique symbol = Symbol("Symbol.asyncDispose");
|
||||
(Symbol as any).asyncDispose ??= asyncDisposeSymbol as unknown as SymbolConstructor["asyncDispose"];
|
||||
(Symbol as any).dispose ??= disposeSymbol as unknown as SymbolConstructor["dispose"];
|
||||
|
||||
// Import needs to be after the workaround
|
||||
import { Rc } from "./rc";
|
||||
|
||||
export class FreeableTestValue {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { SdkLoadService } from "../../abstractions/sdk/sdk-load.service";
|
||||
*
|
||||
* **Warning**: This requires WASM support and will fail if the environment does not support it.
|
||||
*/
|
||||
export class DefaultSdkLoadService implements SdkLoadService {
|
||||
export class DefaultSdkLoadService extends SdkLoadService {
|
||||
async load(): Promise<void> {
|
||||
(sdk as any).init(bitwardenModule);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { UserKey } from "../../../types/key";
|
||||
import { Environment, EnvironmentService } from "../../abstractions/environment.service";
|
||||
import { PlatformUtilsService } from "../../abstractions/platform-utils.service";
|
||||
import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory";
|
||||
import { SdkLoadService } from "../../abstractions/sdk/sdk-load.service";
|
||||
import { UserNotLoggedInError } from "../../abstractions/sdk/sdk.service";
|
||||
import { Rc } from "../../misc/reference-counting/rc";
|
||||
import { EncryptedString } from "../../models/domain/enc-string";
|
||||
@@ -18,6 +19,13 @@ import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key";
|
||||
|
||||
import { DefaultSdkService } from "./default-sdk.service";
|
||||
|
||||
class TestSdkLoadService extends SdkLoadService {
|
||||
protected override load(): Promise<void> {
|
||||
// Simulate successfull WASM load
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
describe("DefaultSdkService", () => {
|
||||
describe("userClient$", () => {
|
||||
let sdkClientFactory!: MockProxy<SdkClientFactory>;
|
||||
@@ -28,7 +36,9 @@ describe("DefaultSdkService", () => {
|
||||
let keyService!: MockProxy<KeyService>;
|
||||
let service!: DefaultSdkService;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
await new TestSdkLoadService().loadAndInit();
|
||||
|
||||
sdkClientFactory = mock<SdkClientFactory>();
|
||||
environmentService = mock<EnvironmentService>();
|
||||
platformUtilsService = mock<PlatformUtilsService>();
|
||||
|
||||
@@ -18,7 +18,6 @@ import { KeyService, KdfConfigService, KdfConfig, KdfType } from "@bitwarden/key
|
||||
import {
|
||||
BitwardenClient,
|
||||
ClientSettings,
|
||||
LogLevel,
|
||||
DeviceType as SdkDeviceType,
|
||||
} from "@bitwarden/sdk-internal";
|
||||
|
||||
@@ -30,6 +29,7 @@ import { UserKey } from "../../../types/key";
|
||||
import { Environment, EnvironmentService } from "../../abstractions/environment.service";
|
||||
import { PlatformUtilsService } from "../../abstractions/platform-utils.service";
|
||||
import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory";
|
||||
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";
|
||||
@@ -47,8 +47,9 @@ export class DefaultSdkService implements SdkService {
|
||||
|
||||
client$ = this.environmentService.environment$.pipe(
|
||||
concatMap(async (env) => {
|
||||
await SdkLoadService.Ready;
|
||||
const settings = this.toSettings(env);
|
||||
return await this.sdkClientFactory.createSdkClient(settings, LogLevel.Info);
|
||||
return await this.sdkClientFactory.createSdkClient(settings);
|
||||
}),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
@@ -135,6 +136,7 @@ export class DefaultSdkService implements SdkService {
|
||||
privateKey$,
|
||||
userKey$,
|
||||
orgKeys$,
|
||||
SdkLoadService.Ready, // Makes sure we wait (once) for the SDK to be loaded
|
||||
]).pipe(
|
||||
// switchMap is required to allow the clean-up logic to be executed when `combineLatest` emits a new value.
|
||||
switchMap(([env, account, kdfParams, privateKey, userKey, orgKeys]) => {
|
||||
@@ -146,7 +148,7 @@ export class DefaultSdkService implements SdkService {
|
||||
}
|
||||
|
||||
const settings = this.toSettings(env);
|
||||
const client = await this.sdkClientFactory.createSdkClient(settings, LogLevel.Info);
|
||||
const client = await this.sdkClientFactory.createSdkClient(settings);
|
||||
|
||||
await this.initializeClient(client, account, kdfParams, privateKey, userKey, orgKeys);
|
||||
|
||||
|
||||
@@ -2,6 +2,6 @@ import { SdkLoadService } from "../../abstractions/sdk/sdk-load.service";
|
||||
|
||||
export class NoopSdkLoadService extends SdkLoadService {
|
||||
async load() {
|
||||
return;
|
||||
throw new Error("SDK not available in this environment");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "core-js/proposals/explicit-resource-management";
|
||||
|
||||
import { webcrypto } from "crypto";
|
||||
|
||||
import { addCustomMatchers } from "./spec";
|
||||
|
||||
Reference in New Issue
Block a user