diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 18883a5fe5..7111b34875 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -419,7 +419,7 @@ export default class MainBackground { this.logService = new ConsoleLogService(isDev); this.cryptoFunctionService = new WebCryptoFunctionService(self); this.keyGenerationService = new KeyGenerationService(this.cryptoFunctionService); - this.storageService = new BrowserLocalStorageService(); + this.storageService = new BrowserLocalStorageService(this.logService); this.intraprocessMessagingSubject = new Subject>>(); diff --git a/apps/browser/src/platform/services/browser-local-storage.service.ts b/apps/browser/src/platform/services/browser-local-storage.service.ts index 61a2653f13..9c315b7f6f 100644 --- a/apps/browser/src/platform/services/browser-local-storage.service.ts +++ b/apps/browser/src/platform/services/browser-local-storage.service.ts @@ -1,10 +1,67 @@ -import AbstractChromeStorageService from "./abstractions/abstract-chrome-storage-api.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; + +import AbstractChromeStorageService, { + SerializedValue, +} from "./abstractions/abstract-chrome-storage-api.service"; export default class BrowserLocalStorageService extends AbstractChromeStorageService { - constructor() { + constructor(private readonly logService: LogService) { super(chrome.storage.local); } + override async get(key: string): Promise { + return await this.getWithRetries(key, 0); + } + + private async getWithRetries(key: string, retryNum: number): Promise { + // See: https://github.com/EFForg/privacybadger/pull/2980 + const MAX_RETRIES = 5; + const WAIT_TIME = 200; + + const store = await this.getStore(key); + + if (store == null) { + if (retryNum >= MAX_RETRIES) { + throw new Error(`Failed to get a value for key '${key}', see logs for more details.`); + } + + retryNum++; + this.logService.warning(`Retrying attempt to get value for key '${key}' in ${WAIT_TIME}ms`); + await new Promise((resolve) => setTimeout(resolve, WAIT_TIME)); + return await this.getWithRetries(key, retryNum); + } + + // We have a store + return this.processGetObject(store[key] as T | SerializedValue); + } + + private async getStore(key: string) { + if (this.chromeStorageApi == null) { + this.logService.warning( + `chrome.storage.local was not initialized while retrieving key '${key}'.`, + ); + return null; + } + + return new Promise<{ [key: string]: unknown }>((resolve) => { + this.chromeStorageApi.get(key, (store) => { + if (chrome.runtime.lastError) { + this.logService.warning(`Failed to get value for key '${key}'`, chrome.runtime.lastError); + resolve(null); + return; + } + + if (store == null) { + this.logService.warning(`Store was empty while retrieving value for key '${key}'`); + resolve(null); + return; + } + + resolve(store); + }); + }); + } + async fillBuffer() { // Write 4MB of data in chrome.storage.local, log files will hold 4MB of data (by default) // before forcing a compaction. To force a compaction and have it remove previously saved data, diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index efbe9ce6bf..129744fd3b 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -304,7 +304,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: AbstractStorageService, useClass: BrowserLocalStorageService, - deps: [], + deps: [LogService], }), safeProvider({ provide: AutofillServiceAbstraction,