diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 764304f4ff..b66f2457b2 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -725,7 +725,7 @@ export default class MainBackground { ); const sdkClientFactory = flagEnabled("sdk") - ? new BrowserSdkClientFactory() + ? new BrowserSdkClientFactory(this.logService) : new NoopSdkClientFactory(); this.sdkService = new DefaultSdkService( sdkClientFactory, 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 index f9cfde73af..6ebbe3ff6b 100644 --- a/apps/browser/src/platform/services/sdk/browser-sdk-client-factory.ts +++ b/apps/browser/src/platform/services/sdk/browser-sdk-client-factory.ts @@ -1,4 +1,6 @@ +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sdk-client-factory"; +import { RecoverableSDKError } from "@bitwarden/common/platform/services/sdk/default-sdk.service"; import type { BitwardenClient } from "@bitwarden/sdk-internal"; import { BrowserApi } from "../../browser/browser-api"; @@ -62,24 +64,39 @@ async function load() { * Works both in popup and service worker. */ export class BrowserSdkClientFactory implements SdkClientFactory { + constructor(private logService: LogService) {} + async createSdkClient( ...args: ConstructorParameters ): Promise { + const startTime = performance.now(); try { await loadWithTimeout(); } catch (error) { throw new Error(`Failed to load: ${error.message}`); } - return Promise.resolve((globalThis as any).init_sdk(...args)); + const endTime = performance.now(); + const elapsed = Math.round((endTime - startTime) / 1000); + + const instance = (globalThis as any).init_sdk(...args); + + this.logService.info("WASM SDK loaded in", Math.round(endTime - startTime), "ms"); + + // If it takes 3 seconds or more to load, we want to capture it. + if (elapsed >= 3) { + throw new RecoverableSDKError(instance, elapsed); + } + + return instance; } } const loadWithTimeout = async () => { return new Promise((resolve, reject) => { const timer = setTimeout(() => { - reject(new Error("Operation timed out after 1 second")); - }, 1000); + reject(new Error("Operation timed out after 10 second")); + }, 10000); load() .then(() => { diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index b68102033b..18d109776a 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -576,8 +576,9 @@ const safeProviders: SafeProvider[] = [ }), safeProvider({ provide: SdkClientFactory, - useClass: flagEnabled("sdk") ? BrowserSdkClientFactory : NoopSdkClientFactory, - deps: [], + useFactory: (logService) => + flagEnabled("sdk") ? new BrowserSdkClientFactory(logService) : new NoopSdkClientFactory(), + deps: [LogService], }), safeProvider({ provide: LoginEmailService, 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 4506319eed..424c32761a 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -32,13 +32,33 @@ import { SdkService } from "../../abstractions/sdk/sdk.service"; import { compareValues } from "../../misc/compare-values"; import { EncryptedString } from "../../models/domain/enc-string"; +export class RecoverableSDKError extends Error { + sdk: BitwardenClient; + timeout: number; + + constructor(sdk: BitwardenClient, timeout: number) { + super(`SDK took ${timeout}s to initialize`); + + this.sdk = sdk; + this.timeout = timeout; + } +} + export class DefaultSdkService implements SdkService { private sdkClientCache = new Map>(); client$ = this.environmentService.environment$.pipe( concatMap(async (env) => { const settings = this.toSettings(env); - return await this.sdkClientFactory.createSdkClient(settings, LogLevel.Info); + try { + return await this.sdkClientFactory.createSdkClient(settings, LogLevel.Info); + } catch (e) { + if (e instanceof RecoverableSDKError) { + await this.failedToInitialize("sdk", e); + return e.sdk; + } + throw e; + } }), shareReplay({ refCount: true, bufferSize: 1 }), );