From c88c5bf1ef2a983f0a8c5589c2d5d83fec85b67d Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 7 Oct 2024 13:20:50 +0200 Subject: [PATCH] [PM-11766] Introduce SDK client (#10974) Integrate the SDK into our other clients. --- apps/browser/config/base.json | 3 +- .../browser/src/background/main.background.ts | 15 +++ apps/browser/src/manifest.v3.json | 3 +- .../sdk/browser-sdk-client-factory.ts | 37 ++++++++ .../src/platform/services/sdk/fallback.ts | 8 ++ .../browser/src/platform/services/sdk/wasm.ts | 8 ++ .../src/popup/services/services.module.ts | 9 ++ apps/browser/webpack.config.js | 14 ++- apps/cli/config/base.json | 5 + apps/cli/config/config.js | 24 ++++- apps/cli/jest.config.js | 10 +- .../service-container/service-container.ts | 16 ++++ apps/cli/tsconfig.json | 2 +- apps/cli/webpack.config.js | 5 + apps/desktop/config/base.json | 6 +- .../src/app/services/services.module.ts | 9 ++ apps/desktop/src/index.html | 2 +- apps/desktop/webpack.renderer.js | 8 +- apps/web/config/base.json | 6 +- apps/web/jest.config.js | 22 +++-- apps/web/src/app/core/core.module.ts | 9 ++ .../app/platform/web-sdk-client-factory.ts | 42 ++++++++ apps/web/webpack.config.js | 8 +- bitwarden_license/bit-cli/jest.config.js | 10 +- bitwarden_license/bit-cli/tsconfig.json | 2 +- .../src/services/jslib-services.module.ts | 8 ++ libs/common/spec/jest-sdk-client-factory.ts | 9 ++ .../abstractions/sdk/sdk-client-factory.ts | 10 ++ .../platform/abstractions/sdk/sdk.service.ts | 8 ++ libs/common/src/platform/misc/flags.ts | 3 +- .../sdk/default-sdk-client-factory.ts | 19 ++++ .../services/sdk/default-sdk.service.ts | 95 +++++++++++++++++++ .../services/sdk/noop-sdk-client-factory.ts | 16 ++++ package-lock.json | 6 ++ package.json | 1 + 35 files changed, 424 insertions(+), 34 deletions(-) create mode 100644 apps/browser/src/platform/services/sdk/browser-sdk-client-factory.ts create mode 100644 apps/browser/src/platform/services/sdk/fallback.ts create mode 100644 apps/browser/src/platform/services/sdk/wasm.ts create mode 100644 apps/cli/config/base.json create mode 100644 apps/web/src/app/platform/web-sdk-client-factory.ts create mode 100644 libs/common/spec/jest-sdk-client-factory.ts create mode 100644 libs/common/src/platform/abstractions/sdk/sdk-client-factory.ts create mode 100644 libs/common/src/platform/abstractions/sdk/sdk.service.ts create mode 100644 libs/common/src/platform/services/sdk/default-sdk-client-factory.ts create mode 100644 libs/common/src/platform/services/sdk/default-sdk.service.ts create mode 100644 libs/common/src/platform/services/sdk/noop-sdk-client-factory.ts diff --git a/apps/browser/config/base.json b/apps/browser/config/base.json index 5113cd7d1b..9506bda0f0 100644 --- a/apps/browser/config/base.json +++ b/apps/browser/config/base.json @@ -2,6 +2,7 @@ "devFlags": {}, "flags": { "showPasswordless": true, - "accountSwitching": false + "accountSwitching": false, + "sdk": false } } diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index d3da7ba0ba..5875490ff0 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -90,6 +90,7 @@ import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platfor import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service"; import { LogService as LogServiceAbstraction } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; import { AbstractStorageService, @@ -122,6 +123,8 @@ import { FileUploadService } from "@bitwarden/common/platform/services/file-uplo import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service"; import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service"; import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner"; +import { DefaultSdkService } from "@bitwarden/common/platform/services/sdk/default-sdk.service"; +import { NoopSdkClientFactory } from "@bitwarden/common/platform/services/sdk/noop-sdk-client-factory"; import { StateService } from "@bitwarden/common/platform/services/state.service"; import { SystemService } from "@bitwarden/common/platform/services/system.service"; import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service"; @@ -228,6 +231,7 @@ import AutofillService from "../autofill/services/autofill.service"; import { SafariApp } from "../browser/safariApp"; import { BackgroundBrowserBiometricsService } from "../key-management/biometrics/background-browser-biometrics.service"; import { BrowserApi } from "../platform/browser/browser-api"; +import { flagEnabled } from "../platform/flags"; import { UpdateBadge } from "../platform/listeners/update-badge"; /* eslint-disable no-restricted-imports */ import { ChromeMessageSender } from "../platform/messaging/chrome-message.sender"; @@ -245,6 +249,7 @@ import { LocalBackedSessionStorageService } from "../platform/services/local-bac import { BackgroundPlatformUtilsService } from "../platform/services/platform-utils/background-platform-utils.service"; import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service"; import { PopupViewCacheBackgroundService } from "../platform/services/popup-view-cache-background.service"; +import { BrowserSdkClientFactory } from "../platform/services/sdk/browser-sdk-client-factory"; import { BackgroundTaskSchedulerService } from "../platform/services/task-scheduler/background-task-scheduler.service"; import { ForegroundTaskSchedulerService } from "../platform/services/task-scheduler/foreground-task-scheduler.service"; import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service"; @@ -364,6 +369,7 @@ export default class MainBackground { syncServiceListener: SyncServiceListener; themeStateService: DefaultThemeStateService; autoSubmitLoginBackground: AutoSubmitLoginBackground; + sdkService: SdkService; onUpdatedRan: boolean; onReplacedRan: boolean; @@ -719,6 +725,15 @@ export default class MainBackground { this.stateProvider, ); + const sdkClientFactory = flagEnabled("sdk") + ? new BrowserSdkClientFactory() + : new NoopSdkClientFactory(); + this.sdkService = new DefaultSdkService( + sdkClientFactory, + this.environmentService, + this.platformUtilsService, + ); + this.passwordStrengthService = new PasswordStrengthService(); this.passwordGenerationService = legacyPasswordGenerationServiceFactory( diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index 5413ee5b63..762e5e01f8 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -39,8 +39,7 @@ } ], "background": { - "service_worker": "background.js", - "type": "module" + "service_worker": "background.js" }, "action": { "default_icon": { diff --git a/apps/browser/src/platform/services/sdk/browser-sdk-client-factory.ts b/apps/browser/src/platform/services/sdk/browser-sdk-client-factory.ts new file mode 100644 index 0000000000..2293a3b384 --- /dev/null +++ b/apps/browser/src/platform/services/sdk/browser-sdk-client-factory.ts @@ -0,0 +1,37 @@ +import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sdk-client-factory"; +import type { BitwardenClient } from "@bitwarden/sdk-internal"; + +// https://stackoverflow.com/a/47880734 +const supported = (() => { + try { + if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") { + const module = new WebAssembly.Module( + Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00), + ); + if (module instanceof WebAssembly.Module) { + return new WebAssembly.Instance(module) instanceof WebAssembly.Instance; + } + } + } catch (e) { + // ignore + } + return false; +})(); + +if (supported) { + // eslint-disable-next-line no-console + console.debug("WebAssembly is supported in this environment"); + import("./wasm"); +} else { + // eslint-disable-next-line no-console + console.debug("WebAssembly is not supported in this environment"); + import("./fallback"); +} + +export class BrowserSdkClientFactory implements SdkClientFactory { + async createSdkClient( + ...args: ConstructorParameters + ): Promise { + return Promise.resolve((globalThis as any).init_sdk(...args)); + } +} diff --git a/apps/browser/src/platform/services/sdk/fallback.ts b/apps/browser/src/platform/services/sdk/fallback.ts new file mode 100644 index 0000000000..82d292fc9e --- /dev/null +++ b/apps/browser/src/platform/services/sdk/fallback.ts @@ -0,0 +1,8 @@ +import * as sdk from "@bitwarden/sdk-internal"; +import * as wasm from "@bitwarden/sdk-internal/bitwarden_wasm_internal_bg.wasm.js"; + +(globalThis as any).init_sdk = (...args: ConstructorParameters) => { + (sdk as any).init(wasm); + + return new sdk.BitwardenClient(...args); +}; diff --git a/apps/browser/src/platform/services/sdk/wasm.ts b/apps/browser/src/platform/services/sdk/wasm.ts new file mode 100644 index 0000000000..1977a171e2 --- /dev/null +++ b/apps/browser/src/platform/services/sdk/wasm.ts @@ -0,0 +1,8 @@ +import * as sdk from "@bitwarden/sdk-internal"; +import * as wasm from "@bitwarden/sdk-internal/bitwarden_wasm_internal_bg.wasm"; + +(globalThis as any).init_sdk = (...args: ConstructorParameters) => { + (sdk as any).init(wasm); + + return new sdk.BitwardenClient(...args); +}; diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 411eed380d..65bcd81072 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -58,6 +58,7 @@ import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/ke import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sdk-client-factory"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { AbstractStorageService, @@ -66,9 +67,11 @@ import { import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging"; // eslint-disable-next-line no-restricted-imports -- Used for dependency injection import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal"; +import { flagEnabled } from "@bitwarden/common/platform/misc/flags"; import { TaskSchedulerService } from "@bitwarden/common/platform/scheduling"; import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; +import { NoopSdkClientFactory } from "@bitwarden/common/platform/services/sdk/noop-sdk-client-factory"; import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider"; import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service"; import { @@ -114,6 +117,7 @@ import BrowserLocalStorageService from "../../platform/services/browser-local-st import { BrowserScriptInjectorService } from "../../platform/services/browser-script-injector.service"; import I18nService from "../../platform/services/i18n.service"; import { ForegroundPlatformUtilsService } from "../../platform/services/platform-utils/foreground-platform-utils.service"; +import { BrowserSdkClientFactory } from "../../platform/services/sdk/browser-sdk-client-factory"; import { ForegroundTaskSchedulerService } from "../../platform/services/task-scheduler/foreground-task-scheduler.service"; import { BrowserStorageServiceProvider } from "../../platform/storage/browser-storage-service.provider"; import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service"; @@ -572,6 +576,11 @@ const safeProviders: SafeProvider[] = [ useClass: ForegroundLockService, deps: [MessageSender, MessageListener], }), + safeProvider({ + provide: SdkClientFactory, + useClass: flagEnabled("sdk") ? BrowserSdkClientFactory : NoopSdkClientFactory, + deps: [], + }), ]; @NgModule({ diff --git a/apps/browser/webpack.config.js b/apps/browser/webpack.config.js index 7ac5a635b1..4309defd3a 100644 --- a/apps/browser/webpack.config.js +++ b/apps/browser/webpack.config.js @@ -122,7 +122,7 @@ const moduleRules = [ loader: "@ngtools/webpack", }, { - test: /\.wasm$/, + test: /argon2(-simd)?\.wasm$/, loader: "base64-loader", type: "javascript/auto", }, @@ -320,9 +320,12 @@ const mainConfig = { clean: true, }, module: { - noParse: /\.wasm$/, + noParse: /argon2(-simd)?\.wasm$/, rules: moduleRules, }, + experiments: { + asyncWebAssembly: true, + }, plugins: plugins, }; @@ -395,12 +398,15 @@ if (manifestVersion == 2) { loader: "ts-loader", }, { - test: /\.wasm$/, + test: /argon2(-simd)?\.wasm$/, loader: "base64-loader", type: "javascript/auto", }, ], - noParse: /\.wasm$/, + noParse: /argon2(-simd)?\.wasm$/, + }, + experiments: { + asyncWebAssembly: true, }, resolve: { extensions: [".ts", ".js"], diff --git a/apps/cli/config/base.json b/apps/cli/config/base.json new file mode 100644 index 0000000000..67f2323e94 --- /dev/null +++ b/apps/cli/config/base.json @@ -0,0 +1,5 @@ +{ + "flags": { + "sdk": false + } +} diff --git a/apps/cli/config/config.js b/apps/cli/config/config.js index 81e2d619fe..cff42ecf62 100644 --- a/apps/cli/config/config.js +++ b/apps/cli/config/config.js @@ -1,7 +1,27 @@ function load(envName) { + const base = require("./base.json"); + const env = loadConfig(envName); + const local = loadConfig("local"); + return { - ...loadConfig(envName), - ...loadConfig("local"), + ...base, + ...env, + ...local, + dev: { + ...base.dev, + ...env.dev, + ...local.dev, + }, + flags: { + ...base.flags, + ...env.flags, + ...local.flags, + }, + devFlags: { + ...base.devFlags, + ...env.devFlags, + ...local.devFlags, + }, }; } diff --git a/apps/cli/jest.config.js b/apps/cli/jest.config.js index 8765ccc8e4..e0a5b9ec9c 100644 --- a/apps/cli/jest.config.js +++ b/apps/cli/jest.config.js @@ -10,7 +10,11 @@ module.exports = { preset: "ts-jest", testEnvironment: "node", setupFilesAfterEnv: ["/test.setup.ts"], - moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "/", - }), + moduleNameMapper: { + "@bitwarden/common/platform/services/sdk/default-sdk-client-factory": + "/../../libs/common/spec/jest-sdk-client-factory", + ...pathsToModuleNameMapper(compilerOptions?.paths || {}, { + prefix: "/", + }), + }, }; diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index 280891edf3..95aa5f98b0 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -64,6 +64,7 @@ import { RegionConfig, } from "@bitwarden/common/platform/abstractions/environment.service"; import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service"; +import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { KeySuffixOptions, LogLevelType } from "@bitwarden/common/platform/enums"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; import { MessageSender } from "@bitwarden/common/platform/messaging"; @@ -86,6 +87,9 @@ import { KeyGenerationService } from "@bitwarden/common/platform/services/key-ge import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service"; import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service"; import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner"; +import { DefaultSdkClientFactory } from "@bitwarden/common/platform/services/sdk/default-sdk-client-factory"; +import { DefaultSdkService } from "@bitwarden/common/platform/services/sdk/default-sdk.service"; +import { NoopSdkClientFactory } from "@bitwarden/common/platform/services/sdk/noop-sdk-client-factory"; import { StateService } from "@bitwarden/common/platform/services/state.service"; import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider"; import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service"; @@ -151,6 +155,7 @@ import { VaultExportServiceAbstraction, } from "@bitwarden/vault-export-core"; +import { flagEnabled } from "../platform/flags"; import { CliPlatformUtilsService } from "../platform/services/cli-platform-utils.service"; import { ConsoleLogService } from "../platform/services/console-log.service"; import { I18nService } from "../platform/services/i18n.service"; @@ -249,6 +254,7 @@ export class ServiceContainer { userAutoUnlockKeyService: UserAutoUnlockKeyService; kdfConfigService: KdfConfigServiceAbstraction; taskSchedulerService: TaskSchedulerService; + sdkService: SdkService; constructor() { let p = null; @@ -522,6 +528,16 @@ export class ServiceContainer { this.globalStateProvider, ); + const sdkClientFactory = flagEnabled("sdk") + ? new DefaultSdkClientFactory() + : new NoopSdkClientFactory(); + this.sdkService = new DefaultSdkService( + sdkClientFactory, + this.environmentService, + this.platformUtilsService, + customUserAgent, + ); + this.passwordStrengthService = new PasswordStrengthService(); this.passwordGenerationService = legacyPasswordGenerationServiceFactory( diff --git a/apps/cli/tsconfig.json b/apps/cli/tsconfig.json index eedf24179d..4cb450f9c6 100644 --- a/apps/cli/tsconfig.json +++ b/apps/cli/tsconfig.json @@ -3,7 +3,7 @@ "pretty": true, "moduleResolution": "node", "target": "ES2016", - "module": "es6", + "module": "ES2020", "noImplicitAny": true, "allowSyntheticDefaultImports": true, "emitDecoratorMetadata": true, diff --git a/apps/cli/webpack.config.js b/apps/cli/webpack.config.js index 2b9c53bac6..d5f66af73e 100644 --- a/apps/cli/webpack.config.js +++ b/apps/cli/webpack.config.js @@ -37,8 +37,10 @@ const plugins = [ contextRegExp: /node-fetch/, }), new webpack.EnvironmentPlugin({ + ENV: ENV, BWCLI_ENV: ENV, FLAGS: envConfig.flags, + DEV_FLAGS: envConfig.devFlags, }), new webpack.IgnorePlugin({ resourceRegExp: /canvas/, @@ -79,6 +81,9 @@ const webpackConfig = { allowlist: [/@bitwarden/], }), ], + experiments: { + asyncWebAssembly: true, + }, }; module.exports = webpackConfig; diff --git a/apps/desktop/config/base.json b/apps/desktop/config/base.json index 3c93018e65..5efcbc629c 100644 --- a/apps/desktop/config/base.json +++ b/apps/desktop/config/base.json @@ -1,4 +1,6 @@ { - "devFlags": {}, - "flags": {} + "flags": { + "sdk": false + }, + "devFlags": {} } diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index c6b73fbbbc..c9b434aa96 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -52,6 +52,7 @@ import { } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sdk-client-factory"; import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/platform/abstractions/system.service"; @@ -60,6 +61,8 @@ import { Message, MessageListener, MessageSender } from "@bitwarden/common/platf import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal"; import { TaskSchedulerService } from "@bitwarden/common/platform/scheduling"; import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service"; +import { DefaultSdkClientFactory } from "@bitwarden/common/platform/services/sdk/default-sdk-client-factory"; +import { NoopSdkClientFactory } from "@bitwarden/common/platform/services/sdk/noop-sdk-client-factory"; import { SystemService } from "@bitwarden/common/platform/services/system.service"; import { GlobalStateProvider, StateProvider } from "@bitwarden/common/platform/state"; // eslint-disable-next-line import/no-restricted-paths -- Implementation for memory storage @@ -73,6 +76,7 @@ import { BiometricStateService, BiometricsService } from "@bitwarden/key-managem import { DesktopAutofillSettingsService } from "../../autofill/services/desktop-autofill-settings.service"; import { ElectronBiometricsService } from "../../key-management/biometrics/electron-biometrics.service"; +import { flagEnabled } from "../../platform/flags"; import { DesktopSettingsService } from "../../platform/services/desktop-settings.service"; import { ElectronCryptoService } from "../../platform/services/electron-crypto.service"; import { ElectronLogRendererService } from "../../platform/services/electron-log.renderer.service"; @@ -302,6 +306,11 @@ const safeProviders: SafeProvider[] = [ InternalUserDecryptionOptionsServiceAbstraction, ], }), + safeProvider({ + provide: SdkClientFactory, + useClass: flagEnabled("sdk") ? DefaultSdkClientFactory : NoopSdkClientFactory, + deps: [], + }), ]; @NgModule({ diff --git a/apps/desktop/src/index.html b/apps/desktop/src/index.html index 6bac674bb5..37eb64adf3 100644 --- a/apps/desktop/src/index.html +++ b/apps/desktop/src/index.html @@ -4,7 +4,7 @@ diff --git a/apps/desktop/webpack.renderer.js b/apps/desktop/webpack.renderer.js index dc3cdf1fef..ac990689ae 100644 --- a/apps/desktop/webpack.renderer.js +++ b/apps/desktop/webpack.renderer.js @@ -42,7 +42,7 @@ const common = { type: "asset/resource", }, { - test: /\.wasm$/, + test: /argon2(-simd)?\.wasm$/, loader: "base64-loader", type: "javascript/auto", }, @@ -143,11 +143,15 @@ const renderer = { parser: { system: true }, }, { - test: /\.wasm$/, + test: /argon2(-simd)?\.wasm$/, loader: "base64-loader", type: "javascript/auto", }, ], + noParse: /argon2(-simd)?\.wasm$/, + }, + experiments: { + asyncWebAssembly: true, }, plugins: [ new AngularWebpackPlugin({ diff --git a/apps/web/config/base.json b/apps/web/config/base.json index 8eb8a31133..98f91360bc 100644 --- a/apps/web/config/base.json +++ b/apps/web/config/base.json @@ -11,6 +11,8 @@ "allowedHosts": "auto" }, "flags": { - "showPasswordless": false - } + "showPasswordless": false, + "sdk": false + }, + "devFlags": {} } diff --git a/apps/web/jest.config.js b/apps/web/jest.config.js index f121823ade..9b5d6fdc76 100644 --- a/apps/web/jest.config.js +++ b/apps/web/jest.config.js @@ -9,11 +9,19 @@ module.exports = { ...sharedConfig, preset: "jest-preset-angular", setupFilesAfterEnv: ["/test.setup.ts"], - moduleNameMapper: pathsToModuleNameMapper( - // lets us use @bitwarden/common/spec in web tests - { "@bitwarden/common/spec": ["../../libs/common/spec"], ...(compilerOptions?.paths ?? {}) }, - { - prefix: "/", - }, - ), + moduleNameMapper: { + // Replace ESM SDK with Node compatible SDK + "@bitwarden/common/platform/services/sdk/default-sdk-client-factory": + "/../../libs/common/spec/jest-sdk-client-factory", + ...pathsToModuleNameMapper( + { + // lets us use @bitwarden/common/spec in web tests + "@bitwarden/common/spec": ["../../libs/common/spec"], + ...(compilerOptions?.paths ?? {}), + }, + { + prefix: "/", + }, + ), + }, }; diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index ceff268217..217b228d32 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -51,6 +51,7 @@ import { FileDownloadService } from "@bitwarden/common/platform/abstractions/fil import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sdk-client-factory"; import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; import { ThemeType } from "@bitwarden/common/platform/enums"; import { AppIdService as DefaultAppIdService } from "@bitwarden/common/platform/services/app-id.service"; @@ -58,6 +59,7 @@ import { MemoryStorageService } from "@bitwarden/common/platform/services/memory // eslint-disable-next-line import/no-restricted-paths -- Implementation for memory storage import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service"; import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner"; +import { NoopSdkClientFactory } from "@bitwarden/common/platform/services/sdk/noop-sdk-client-factory"; import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider"; /* eslint-disable import/no-restricted-paths -- Implementation for memory storage */ import { GlobalStateProvider, StateProvider } from "@bitwarden/common/platform/state"; @@ -72,6 +74,7 @@ import { VaultTimeout, VaultTimeoutStringType } from "@bitwarden/common/types/va import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { BiometricsService } from "@bitwarden/key-management"; +import { flagEnabled } from "../../utils/flags"; import { PolicyListService } from "../admin-console/core/policy-list.service"; import { WebSetPasswordJitService, @@ -84,6 +87,7 @@ import { I18nService } from "../core/i18n.service"; import { WebBiometricsService } from "../key-management/web-biometric.service"; import { WebEnvironmentService } from "../platform/web-environment.service"; import { WebMigrationRunner } from "../platform/web-migration-runner"; +import { WebSdkClientFactory } from "../platform/web-sdk-client-factory"; import { WebStorageServiceProvider } from "../platform/web-storage-service.provider"; import { EventService } from "./event.service"; @@ -245,6 +249,11 @@ const safeProviders: SafeProvider[] = [ useClass: DefaultCollectionAdminService, deps: [ApiService, CryptoServiceAbstraction, EncryptService, CollectionService], }), + safeProvider({ + provide: SdkClientFactory, + useClass: flagEnabled("sdk") ? WebSdkClientFactory : NoopSdkClientFactory, + deps: [], + }), ]; @NgModule({ diff --git a/apps/web/src/app/platform/web-sdk-client-factory.ts b/apps/web/src/app/platform/web-sdk-client-factory.ts new file mode 100644 index 0000000000..2ebb2bcc10 --- /dev/null +++ b/apps/web/src/app/platform/web-sdk-client-factory.ts @@ -0,0 +1,42 @@ +import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sdk-client-factory"; +import * as sdk from "@bitwarden/sdk-internal"; + +/** + * SDK client factory with a js fallback for when WASM is not supported. + */ +export class WebSdkClientFactory implements SdkClientFactory { + async createSdkClient( + ...args: ConstructorParameters + ): Promise { + const module = await load(); + + (sdk as any).init(module); + + return Promise.resolve(new sdk.BitwardenClient(...args)); + } +} + +// https://stackoverflow.com/a/47880734 +const supported = (() => { + try { + if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") { + const module = new WebAssembly.Module( + Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00), + ); + if (module instanceof WebAssembly.Module) { + return new WebAssembly.Instance(module) instanceof WebAssembly.Instance; + } + } + } catch (e) { + // ignore + } + return false; +})(); + +async function load() { + if (supported) { + return await import("@bitwarden/sdk-internal/bitwarden_wasm_internal_bg.wasm"); + } else { + return await import("@bitwarden/sdk-internal/bitwarden_wasm_internal_bg.wasm.js"); + } +} diff --git a/apps/web/webpack.config.js b/apps/web/webpack.config.js index fee60264ea..df325015aa 100644 --- a/apps/web/webpack.config.js +++ b/apps/web/webpack.config.js @@ -78,7 +78,7 @@ const moduleRules = [ loader: "@ngtools/webpack", }, { - test: /\.wasm$/, + test: /argon2(-simd)?\.wasm$/, loader: "base64-loader", type: "javascript/auto", }, @@ -324,6 +324,7 @@ const webpackConfig = { mode: NODE_ENV, devtool: "source-map", devServer: devServer, + target: "web", entry: { "app/polyfills": "./src/polyfills.ts", "app/main": "./src/main.ts", @@ -383,9 +384,12 @@ const webpackConfig = { clean: true, }, module: { - noParse: /\.wasm$/, + noParse: /argon2(-simd)?\.wasm$/, rules: moduleRules, }, + experiments: { + asyncWebAssembly: true, + }, plugins: plugins, }; diff --git a/bitwarden_license/bit-cli/jest.config.js b/bitwarden_license/bit-cli/jest.config.js index 92be98cc56..30c9784c32 100644 --- a/bitwarden_license/bit-cli/jest.config.js +++ b/bitwarden_license/bit-cli/jest.config.js @@ -10,7 +10,11 @@ module.exports = { preset: "ts-jest", testEnvironment: "node", setupFilesAfterEnv: ["/../../apps/cli/test.setup.ts"], - moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "/", - }), + moduleNameMapper: { + "@bitwarden/common/platform/services/sdk/default-sdk-client-factory": + "/../../libs/common/spec/jest-sdk-client-factory", + ...pathsToModuleNameMapper(compilerOptions?.paths || {}, { + prefix: "/", + }), + }, }; diff --git a/bitwarden_license/bit-cli/tsconfig.json b/bitwarden_license/bit-cli/tsconfig.json index bb9986e6c9..9440a03375 100644 --- a/bitwarden_license/bit-cli/tsconfig.json +++ b/bitwarden_license/bit-cli/tsconfig.json @@ -3,7 +3,7 @@ "pretty": true, "moduleResolution": "node", "target": "ES2016", - "module": "es6", + "module": "ES2020", "noImplicitAny": true, "allowSyntheticDefaultImports": true, "emitDecoratorMetadata": true, diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 934649ebfc..c8186e1e90 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -151,6 +151,8 @@ import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwar import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sdk-client-factory"; +import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; import { ValidationService as ValidationServiceAbstraction } from "@bitwarden/common/platform/abstractions/validation.service"; @@ -179,6 +181,7 @@ import { KeyGenerationService } from "@bitwarden/common/platform/services/key-ge import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service"; import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner"; import { NoopNotificationsService } from "@bitwarden/common/platform/services/noop-notifications.service"; +import { DefaultSdkService } from "@bitwarden/common/platform/services/sdk/default-sdk.service"; import { StateService } from "@bitwarden/common/platform/services/state.service"; import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider"; import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service"; @@ -1324,6 +1327,11 @@ const safeProviders: SafeProvider[] = [ useExisting: NoopViewCacheService, deps: [], }), + safeProvider({ + provide: SdkService, + useClass: DefaultSdkService, + deps: [SdkClientFactory, EnvironmentService, PlatformUtilsServiceAbstraction], + }), ]; @NgModule({ diff --git a/libs/common/spec/jest-sdk-client-factory.ts b/libs/common/spec/jest-sdk-client-factory.ts new file mode 100644 index 0000000000..ff120ccd78 --- /dev/null +++ b/libs/common/spec/jest-sdk-client-factory.ts @@ -0,0 +1,9 @@ +import { ClientSettings, LogLevel, BitwardenClient } from "@bitwarden/sdk-internal"; + +import { SdkClientFactory } from "../src/platform/abstractions/sdk/sdk-client-factory"; + +export class DefaultSdkClientFactory implements SdkClientFactory { + createSdkClient(settings?: ClientSettings, log_level?: LogLevel): Promise { + throw new Error("Method not implemented."); + } +} diff --git a/libs/common/src/platform/abstractions/sdk/sdk-client-factory.ts b/libs/common/src/platform/abstractions/sdk/sdk-client-factory.ts new file mode 100644 index 0000000000..d684561dac --- /dev/null +++ b/libs/common/src/platform/abstractions/sdk/sdk-client-factory.ts @@ -0,0 +1,10 @@ +import type { BitwardenClient } from "@bitwarden/sdk-internal"; + +/** + * Factory for creating SDK clients. + */ +export abstract class SdkClientFactory { + abstract createSdkClient( + ...args: ConstructorParameters + ): Promise; +} diff --git a/libs/common/src/platform/abstractions/sdk/sdk.service.ts b/libs/common/src/platform/abstractions/sdk/sdk.service.ts new file mode 100644 index 0000000000..5899856e5f --- /dev/null +++ b/libs/common/src/platform/abstractions/sdk/sdk.service.ts @@ -0,0 +1,8 @@ +import { Observable } from "rxjs"; + +import { BitwardenClient } from "@bitwarden/sdk-internal"; + +export abstract class SdkService { + client$: Observable; + supported$: Observable; +} diff --git a/libs/common/src/platform/misc/flags.ts b/libs/common/src/platform/misc/flags.ts index 3a30567681..b3269c8f4e 100644 --- a/libs/common/src/platform/misc/flags.ts +++ b/libs/common/src/platform/misc/flags.ts @@ -2,6 +2,7 @@ // eslint-disable-next-line @typescript-eslint/ban-types export type SharedFlags = { showPasswordless?: boolean; + sdk?: boolean; }; // required to avoid linting errors when there are no flags @@ -28,7 +29,7 @@ function getFlags(envFlags: string | T): T { * @returns The value of the flag */ export function flagEnabled(flag: keyof Flags): boolean { - const flags = getFlags(process.env.FLAGS); + const flags = getFlags(process.env.FLAGS) ?? ({} as Flags); return flags[flag] == null || !!flags[flag]; } diff --git a/libs/common/src/platform/services/sdk/default-sdk-client-factory.ts b/libs/common/src/platform/services/sdk/default-sdk-client-factory.ts new file mode 100644 index 0000000000..8e99af2efe --- /dev/null +++ b/libs/common/src/platform/services/sdk/default-sdk-client-factory.ts @@ -0,0 +1,19 @@ +import * as sdk from "@bitwarden/sdk-internal"; +import * as module from "@bitwarden/sdk-internal/bitwarden_wasm_internal_bg.wasm"; + +import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory"; + +/** + * Directly imports the Bitwarden SDK and initializes it. + * + * **Warning**: This requires WASM support and will fail if the environment does not support it. + */ +export class DefaultSdkClientFactory implements SdkClientFactory { + async createSdkClient( + ...args: ConstructorParameters + ): Promise { + (sdk as any).init(module); + + return Promise.resolve(new sdk.BitwardenClient(...args)); + } +} diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts new file mode 100644 index 0000000000..0240ebc94b --- /dev/null +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -0,0 +1,95 @@ +import { concatMap, shareReplay } from "rxjs"; + +import { LogLevel, DeviceType as SdkDeviceType } from "@bitwarden/sdk-internal"; + +import { DeviceType } from "../../../enums/device-type.enum"; +import { EnvironmentService } from "../../abstractions/environment.service"; +import { PlatformUtilsService } from "../../abstractions/platform-utils.service"; +import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory"; +import { SdkService } from "../../abstractions/sdk/sdk.service"; + +export class DefaultSdkService implements SdkService { + client$ = this.environmentService.environment$.pipe( + concatMap(async (env) => { + const settings = { + apiUrl: env.getApiUrl(), + identityUrl: env.getIdentityUrl(), + deviceType: this.toDevice(this.platformUtilsService.getDevice()), + userAgent: this.userAgent ?? navigator.userAgent, + }; + + return await this.sdkClientFactory.createSdkClient(settings, LogLevel.Info); + }), + shareReplay({ refCount: true, bufferSize: 1 }), + ); + + supported$ = this.client$.pipe( + concatMap(async (client) => { + return client.echo("bitwarden wasm!") === "bitwarden wasm!"; + }), + ); + + constructor( + private sdkClientFactory: SdkClientFactory, + private environmentService: EnvironmentService, + private platformUtilsService: PlatformUtilsService, + private userAgent: string = null, + ) {} + + private toDevice(device: DeviceType): SdkDeviceType { + switch (device) { + case DeviceType.Android: + return "Android"; + case DeviceType.iOS: + return "iOS"; + case DeviceType.ChromeExtension: + return "ChromeExtension"; + case DeviceType.FirefoxExtension: + return "FirefoxExtension"; + case DeviceType.OperaExtension: + return "OperaExtension"; + case DeviceType.EdgeExtension: + return "EdgeExtension"; + case DeviceType.WindowsDesktop: + return "WindowsDesktop"; + case DeviceType.MacOsDesktop: + return "MacOsDesktop"; + case DeviceType.LinuxDesktop: + return "LinuxDesktop"; + case DeviceType.ChromeBrowser: + return "ChromeBrowser"; + case DeviceType.FirefoxBrowser: + return "FirefoxBrowser"; + case DeviceType.OperaBrowser: + return "OperaBrowser"; + case DeviceType.EdgeBrowser: + return "EdgeBrowser"; + case DeviceType.IEBrowser: + return "IEBrowser"; + case DeviceType.UnknownBrowser: + return "UnknownBrowser"; + case DeviceType.AndroidAmazon: + return "AndroidAmazon"; + case DeviceType.UWP: + return "UWP"; + case DeviceType.SafariBrowser: + return "SafariBrowser"; + case DeviceType.VivaldiBrowser: + return "VivaldiBrowser"; + case DeviceType.VivaldiExtension: + return "VivaldiExtension"; + case DeviceType.SafariExtension: + return "SafariExtension"; + case DeviceType.Server: + return "Server"; + case DeviceType.WindowsCLI: + return "WindowsCLI"; + case DeviceType.MacOsCLI: + return "MacOsCLI"; + case DeviceType.LinuxCLI: + return "LinuxCLI"; + default: + return "SDK"; + } + } +} diff --git a/libs/common/src/platform/services/sdk/noop-sdk-client-factory.ts b/libs/common/src/platform/services/sdk/noop-sdk-client-factory.ts new file mode 100644 index 0000000000..d7eab7e8dc --- /dev/null +++ b/libs/common/src/platform/services/sdk/noop-sdk-client-factory.ts @@ -0,0 +1,16 @@ +import type { BitwardenClient } from "@bitwarden/sdk-internal"; + +import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory"; + +/** + * Noop SDK client factory. + * + * Used during SDK rollout to prevent bundling the SDK with some applications. + */ +export class NoopSdkClientFactory implements SdkClientFactory { + createSdkClient( + ...args: ConstructorParameters + ): Promise { + return Promise.reject(new Error("SDK not available")); + } +} diff --git a/package-lock.json b/package-lock.json index f5e233c232..7ef9cf23d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "@angular/platform-browser": "16.2.12", "@angular/platform-browser-dynamic": "16.2.12", "@angular/router": "16.2.12", + "@bitwarden/sdk-internal": "0.1.3", "@electron/fuses": "1.8.0", "@koa/multer": "3.0.2", "@koa/router": "12.0.1", @@ -4625,6 +4626,11 @@ "resolved": "libs/platform", "link": true }, + "node_modules/@bitwarden/sdk-internal": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.1.3.tgz", + "integrity": "sha512-zk9DyYMjylVLdljeLn3OLBcD939Hg/qMNJ2FxbyjiSKtcOcgglXgYmbcS01NRFFfM9REbn+j+2fWbQo6N+8SHw==" + }, "node_modules/@bitwarden/vault": { "resolved": "libs/vault", "link": true diff --git a/package.json b/package.json index ed4e95c012..2926fd095f 100644 --- a/package.json +++ b/package.json @@ -157,6 +157,7 @@ "@angular/platform-browser": "16.2.12", "@angular/platform-browser-dynamic": "16.2.12", "@angular/router": "16.2.12", + "@bitwarden/sdk-internal": "0.1.3", "@electron/fuses": "1.8.0", "@koa/multer": "3.0.2", "@koa/router": "12.0.1",