mirror of
https://github.com/bitwarden/browser.git
synced 2025-02-28 03:21:40 +01: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:
parent
44d50a70c2
commit
ce5a5e3649
@ -1,5 +1,7 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
|
import "core-js/proposals/explicit-resource-management";
|
||||||
|
|
||||||
import { filter, firstValueFrom, map, merge, Subject, timeout } from "rxjs";
|
import { filter, firstValueFrom, map, merge, Subject, timeout } from "rxjs";
|
||||||
|
|
||||||
import { CollectionService, DefaultCollectionService } from "@bitwarden/admin-console/common";
|
import { CollectionService, DefaultCollectionService } from "@bitwarden/admin-console/common";
|
||||||
@ -1290,7 +1292,7 @@ export default class MainBackground {
|
|||||||
}
|
}
|
||||||
this.containerService.attachToGlobal(self);
|
this.containerService.attachToGlobal(self);
|
||||||
|
|
||||||
await this.sdkLoadService.load();
|
await this.sdkLoadService.loadAndInit();
|
||||||
// Only the "true" background should run migrations
|
// Only the "true" background should run migrations
|
||||||
await this.stateService.init({ runMigrations: true });
|
await this.stateService.init({ runMigrations: true });
|
||||||
|
|
||||||
|
@ -60,8 +60,10 @@ async function importModule(): Promise<GlobalWithWasmInit["initSdk"]> {
|
|||||||
return (globalThis as GlobalWithWasmInit).initSdk;
|
return (globalThis as GlobalWithWasmInit).initSdk;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BrowserSdkLoadService implements SdkLoadService {
|
export class BrowserSdkLoadService extends SdkLoadService {
|
||||||
constructor(readonly logService: LogService) {}
|
constructor(readonly logService: LogService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
async load(): Promise<void> {
|
async load(): Promise<void> {
|
||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
import "core-js/stable";
|
import "core-js/stable";
|
||||||
|
import "core-js/proposals/explicit-resource-management";
|
||||||
import "zone.js";
|
import "zone.js";
|
||||||
|
@ -32,7 +32,7 @@ export class InitService {
|
|||||||
|
|
||||||
init() {
|
init() {
|
||||||
return async () => {
|
return async () => {
|
||||||
await this.sdkLoadService.load();
|
await this.sdkLoadService.loadAndInit();
|
||||||
await this.stateService.init({ runMigrations: false }); // Browser background is responsible for migrations
|
await this.stateService.init({ runMigrations: false }); // Browser background is responsible for migrations
|
||||||
await this.i18nService.init();
|
await this.i18nService.init();
|
||||||
this.twoFactorService.init();
|
this.twoFactorService.init();
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import "core-js/proposals/explicit-resource-management";
|
||||||
|
|
||||||
import { program } from "commander";
|
import { program } from "commander";
|
||||||
|
|
||||||
import { OssServeConfigurator } from "./oss-serve-configurator";
|
import { OssServeConfigurator } from "./oss-serve-configurator";
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
|
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
|
||||||
import * as sdk from "@bitwarden/sdk-internal";
|
import * as sdk from "@bitwarden/sdk-internal";
|
||||||
|
|
||||||
export class CliSdkLoadService implements SdkLoadService {
|
export class CliSdkLoadService extends SdkLoadService {
|
||||||
async load(): Promise<void> {
|
async load(): Promise<void> {
|
||||||
const module = await import("@bitwarden/sdk-internal/bitwarden_wasm_internal_bg.wasm");
|
const module = await import("@bitwarden/sdk-internal/bitwarden_wasm_internal_bg.wasm");
|
||||||
(sdk as any).init(module);
|
(sdk as any).init(module);
|
||||||
|
@ -867,7 +867,7 @@ export class ServiceContainer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.sdkLoadService.load();
|
await this.sdkLoadService.loadAndInit();
|
||||||
await this.storageService.init();
|
await this.storageService.init();
|
||||||
await this.stateService.init();
|
await this.stateService.init();
|
||||||
this.containerService.attachToGlobal(global);
|
this.containerService.attachToGlobal(global);
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import "core-js/proposals/explicit-resource-management";
|
||||||
|
|
||||||
import { enableProdMode } from "@angular/core";
|
import { enableProdMode } from "@angular/core";
|
||||||
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
|
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ export class InitService {
|
|||||||
|
|
||||||
init() {
|
init() {
|
||||||
return async () => {
|
return async () => {
|
||||||
await this.sdkLoadService.load();
|
await this.sdkLoadService.loadAndInit();
|
||||||
await this.sshAgentService.init();
|
await this.sshAgentService.init();
|
||||||
this.nativeMessagingService.init();
|
this.nativeMessagingService.init();
|
||||||
await this.stateService.init({ runMigrations: false }); // Desktop will run them in main process
|
await this.stateService.init({ runMigrations: false }); // Desktop will run them in main process
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
|
import "core-js/proposals/explicit-resource-management";
|
||||||
|
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
|
||||||
import { app } from "electron";
|
import { app } from "electron";
|
||||||
|
@ -42,7 +42,7 @@ export class InitService {
|
|||||||
|
|
||||||
init() {
|
init() {
|
||||||
return async () => {
|
return async () => {
|
||||||
await this.sdkLoadService.load();
|
await this.sdkLoadService.loadAndInit();
|
||||||
await this.stateService.init();
|
await this.stateService.init();
|
||||||
|
|
||||||
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
|
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
|
||||||
|
@ -18,7 +18,7 @@ const supported = (() => {
|
|||||||
return false;
|
return false;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
export class WebSdkLoadService implements SdkLoadService {
|
export class WebSdkLoadService extends SdkLoadService {
|
||||||
async load(): Promise<void> {
|
async load(): Promise<void> {
|
||||||
let module: any;
|
let module: any;
|
||||||
if (supported) {
|
if (supported) {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import "core-js/stable";
|
import "core-js/stable";
|
||||||
|
import "core-js/proposals/explicit-resource-management";
|
||||||
import "zone.js";
|
import "zone.js";
|
||||||
|
|
||||||
if (process.env.NODE_ENV === "production") {
|
if (process.env.NODE_ENV === "production") {
|
||||||
|
@ -1,3 +1,52 @@
|
|||||||
export abstract class SdkLoadService {
|
import { init_sdk } from "@bitwarden/sdk-internal";
|
||||||
abstract load(): Promise<void>;
|
|
||||||
|
// 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";
|
import { Rc } from "./rc";
|
||||||
|
|
||||||
export class FreeableTestValue {
|
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.
|
* **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> {
|
async load(): Promise<void> {
|
||||||
(sdk as any).init(bitwardenModule);
|
(sdk as any).init(bitwardenModule);
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import { UserKey } from "../../../types/key";
|
|||||||
import { Environment, EnvironmentService } from "../../abstractions/environment.service";
|
import { Environment, EnvironmentService } from "../../abstractions/environment.service";
|
||||||
import { PlatformUtilsService } from "../../abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "../../abstractions/platform-utils.service";
|
||||||
import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory";
|
import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory";
|
||||||
|
import { SdkLoadService } from "../../abstractions/sdk/sdk-load.service";
|
||||||
import { UserNotLoggedInError } from "../../abstractions/sdk/sdk.service";
|
import { UserNotLoggedInError } from "../../abstractions/sdk/sdk.service";
|
||||||
import { Rc } from "../../misc/reference-counting/rc";
|
import { Rc } from "../../misc/reference-counting/rc";
|
||||||
import { EncryptedString } from "../../models/domain/enc-string";
|
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";
|
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("DefaultSdkService", () => {
|
||||||
describe("userClient$", () => {
|
describe("userClient$", () => {
|
||||||
let sdkClientFactory!: MockProxy<SdkClientFactory>;
|
let sdkClientFactory!: MockProxy<SdkClientFactory>;
|
||||||
@ -28,7 +36,9 @@ describe("DefaultSdkService", () => {
|
|||||||
let keyService!: MockProxy<KeyService>;
|
let keyService!: MockProxy<KeyService>;
|
||||||
let service!: DefaultSdkService;
|
let service!: DefaultSdkService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
|
await new TestSdkLoadService().loadAndInit();
|
||||||
|
|
||||||
sdkClientFactory = mock<SdkClientFactory>();
|
sdkClientFactory = mock<SdkClientFactory>();
|
||||||
environmentService = mock<EnvironmentService>();
|
environmentService = mock<EnvironmentService>();
|
||||||
platformUtilsService = mock<PlatformUtilsService>();
|
platformUtilsService = mock<PlatformUtilsService>();
|
||||||
|
@ -18,7 +18,6 @@ import { KeyService, KdfConfigService, KdfConfig, KdfType } from "@bitwarden/key
|
|||||||
import {
|
import {
|
||||||
BitwardenClient,
|
BitwardenClient,
|
||||||
ClientSettings,
|
ClientSettings,
|
||||||
LogLevel,
|
|
||||||
DeviceType as SdkDeviceType,
|
DeviceType as SdkDeviceType,
|
||||||
} from "@bitwarden/sdk-internal";
|
} from "@bitwarden/sdk-internal";
|
||||||
|
|
||||||
@ -30,6 +29,7 @@ import { UserKey } from "../../../types/key";
|
|||||||
import { Environment, EnvironmentService } from "../../abstractions/environment.service";
|
import { Environment, EnvironmentService } from "../../abstractions/environment.service";
|
||||||
import { PlatformUtilsService } from "../../abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "../../abstractions/platform-utils.service";
|
||||||
import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory";
|
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 { SdkService, UserNotLoggedInError } from "../../abstractions/sdk/sdk.service";
|
||||||
import { compareValues } from "../../misc/compare-values";
|
import { compareValues } from "../../misc/compare-values";
|
||||||
import { Rc } from "../../misc/reference-counting/rc";
|
import { Rc } from "../../misc/reference-counting/rc";
|
||||||
@ -47,8 +47,9 @@ export class DefaultSdkService implements SdkService {
|
|||||||
|
|
||||||
client$ = this.environmentService.environment$.pipe(
|
client$ = this.environmentService.environment$.pipe(
|
||||||
concatMap(async (env) => {
|
concatMap(async (env) => {
|
||||||
|
await SdkLoadService.Ready;
|
||||||
const settings = this.toSettings(env);
|
const settings = this.toSettings(env);
|
||||||
return await this.sdkClientFactory.createSdkClient(settings, LogLevel.Info);
|
return await this.sdkClientFactory.createSdkClient(settings);
|
||||||
}),
|
}),
|
||||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||||
);
|
);
|
||||||
@ -135,6 +136,7 @@ export class DefaultSdkService implements SdkService {
|
|||||||
privateKey$,
|
privateKey$,
|
||||||
userKey$,
|
userKey$,
|
||||||
orgKeys$,
|
orgKeys$,
|
||||||
|
SdkLoadService.Ready, // Makes sure we wait (once) for the SDK to be loaded
|
||||||
]).pipe(
|
]).pipe(
|
||||||
// switchMap is required to allow the clean-up logic to be executed when `combineLatest` emits a new value.
|
// 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]) => {
|
switchMap(([env, account, kdfParams, privateKey, userKey, orgKeys]) => {
|
||||||
@ -146,7 +148,7 @@ export class DefaultSdkService implements SdkService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const settings = this.toSettings(env);
|
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);
|
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 {
|
export class NoopSdkLoadService extends SdkLoadService {
|
||||||
async load() {
|
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 { webcrypto } from "crypto";
|
||||||
|
|
||||||
import { addCustomMatchers } from "./spec";
|
import { addCustomMatchers } from "./spec";
|
||||||
|
8
package-lock.json
generated
8
package-lock.json
generated
@ -24,7 +24,7 @@
|
|||||||
"@angular/platform-browser": "18.2.13",
|
"@angular/platform-browser": "18.2.13",
|
||||||
"@angular/platform-browser-dynamic": "18.2.13",
|
"@angular/platform-browser-dynamic": "18.2.13",
|
||||||
"@angular/router": "18.2.13",
|
"@angular/router": "18.2.13",
|
||||||
"@bitwarden/sdk-internal": "0.2.0-main.105",
|
"@bitwarden/sdk-internal": "0.2.0-main.107",
|
||||||
"@electron/fuses": "1.8.0",
|
"@electron/fuses": "1.8.0",
|
||||||
"@emotion/css": "11.13.5",
|
"@emotion/css": "11.13.5",
|
||||||
"@koa/multer": "3.0.2",
|
"@koa/multer": "3.0.2",
|
||||||
@ -4651,9 +4651,9 @@
|
|||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
"node_modules/@bitwarden/sdk-internal": {
|
"node_modules/@bitwarden/sdk-internal": {
|
||||||
"version": "0.2.0-main.105",
|
"version": "0.2.0-main.107",
|
||||||
"resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.105.tgz",
|
"resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.107.tgz",
|
||||||
"integrity": "sha512-MaQFJbuKTCbN9oZC/+opYVeegaNNJpiUv9/zx+gu8KxWmX0hyEkNPtHKxBjDt3kLLz69CudDtUxEgqOfcDsYAw==",
|
"integrity": "sha512-xpOF6NAS0/em3jFBv4FI1ASy1Nuc7I1v41TVmG56wS+80y+NH1RnfGjp+a+XiO7Xxh3jssrxmjzihJjWQQA0rg==",
|
||||||
"license": "GPL-3.0"
|
"license": "GPL-3.0"
|
||||||
},
|
},
|
||||||
"node_modules/@bitwarden/send-ui": {
|
"node_modules/@bitwarden/send-ui": {
|
||||||
|
@ -154,7 +154,7 @@
|
|||||||
"@angular/platform-browser": "18.2.13",
|
"@angular/platform-browser": "18.2.13",
|
||||||
"@angular/platform-browser-dynamic": "18.2.13",
|
"@angular/platform-browser-dynamic": "18.2.13",
|
||||||
"@angular/router": "18.2.13",
|
"@angular/router": "18.2.13",
|
||||||
"@bitwarden/sdk-internal": "0.2.0-main.105",
|
"@bitwarden/sdk-internal": "0.2.0-main.107",
|
||||||
"@electron/fuses": "1.8.0",
|
"@electron/fuses": "1.8.0",
|
||||||
"@emotion/css": "11.13.5",
|
"@emotion/css": "11.13.5",
|
||||||
"@koa/multer": "3.0.2",
|
"@koa/multer": "3.0.2",
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"target": "ES2016",
|
"target": "ES2016",
|
||||||
"module": "ES2020",
|
"module": "ES2020",
|
||||||
"lib": ["es5", "es6", "es7", "dom", "ES2021"],
|
"lib": ["es5", "es6", "es7", "dom", "ES2021", "ESNext.Disposable"],
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
|
Loading…
Reference in New Issue
Block a user