mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-02 18:17:46 +01:00
Remove usages of chrome.storage
(#8664)
* Enable clearing and retrieving all values from local storage I didn't add these methods to the base abstract class because we don't currently have a use case for them. If we develop one, we can just lift it up. * Use new browser local storage methods for reseed task * Remove the now dangerous methods enabling usage of `chrome.storage` Any direct reference to chrome storage needs to handle serialization tags, which is best dealt with through the classes implementing `AbstractChromeStorageService`
This commit is contained in:
parent
9fb3e9b3ee
commit
560033cb88
@ -8,7 +8,6 @@ import {
|
|||||||
generateRandomCustomElementName,
|
generateRandomCustomElementName,
|
||||||
sendExtensionMessage,
|
sendExtensionMessage,
|
||||||
setElementStyles,
|
setElementStyles,
|
||||||
getFromLocalStorage,
|
|
||||||
setupExtensionDisconnectAction,
|
setupExtensionDisconnectAction,
|
||||||
setupAutofillInitDisconnectAction,
|
setupAutofillInitDisconnectAction,
|
||||||
} from "./index";
|
} from "./index";
|
||||||
@ -124,33 +123,6 @@ describe("setElementStyles", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getFromLocalStorage", () => {
|
|
||||||
it("returns a promise with the storage object pulled from the extension storage api", async () => {
|
|
||||||
const localStorage: Record<string, any> = {
|
|
||||||
testValue: "test",
|
|
||||||
another: "another",
|
|
||||||
};
|
|
||||||
jest.spyOn(chrome.storage.local, "get").mockImplementation((keys, callback) => {
|
|
||||||
const localStorageObject: Record<string, string> = {};
|
|
||||||
|
|
||||||
if (typeof keys === "string") {
|
|
||||||
localStorageObject[keys] = localStorage[keys];
|
|
||||||
} else if (Array.isArray(keys)) {
|
|
||||||
for (const key of keys) {
|
|
||||||
localStorageObject[key] = localStorage[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(localStorageObject);
|
|
||||||
});
|
|
||||||
|
|
||||||
const returnValue = await getFromLocalStorage("testValue");
|
|
||||||
|
|
||||||
expect(chrome.storage.local.get).toHaveBeenCalled();
|
|
||||||
expect(returnValue).toEqual({ testValue: "test" });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("setupExtensionDisconnectAction", () => {
|
describe("setupExtensionDisconnectAction", () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
@ -106,18 +106,6 @@ function setElementStyles(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get data from local storage based on the keys provided.
|
|
||||||
*
|
|
||||||
* @param keys - String or array of strings of keys to get from local storage
|
|
||||||
* @deprecated Do not call this, use state-relevant services instead
|
|
||||||
*/
|
|
||||||
async function getFromLocalStorage(keys: string | string[]): Promise<Record<string, any>> {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
chrome.storage.local.get(keys, (storage: Record<string, any>) => resolve(storage));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up a long-lived connection with the extension background
|
* Sets up a long-lived connection with the extension background
|
||||||
* and triggers an onDisconnect event if the extension context
|
* and triggers an onDisconnect event if the extension context
|
||||||
@ -278,7 +266,6 @@ export {
|
|||||||
buildSvgDomElement,
|
buildSvgDomElement,
|
||||||
sendExtensionMessage,
|
sendExtensionMessage,
|
||||||
setElementStyles,
|
setElementStyles,
|
||||||
getFromLocalStorage,
|
|
||||||
setupExtensionDisconnectAction,
|
setupExtensionDisconnectAction,
|
||||||
setupAutofillInitDisconnectAction,
|
setupAutofillInitDisconnectAction,
|
||||||
elementIsFillableFormField,
|
elementIsFillableFormField,
|
||||||
|
@ -234,7 +234,7 @@ import RuntimeBackground from "./runtime.background";
|
|||||||
|
|
||||||
export default class MainBackground {
|
export default class MainBackground {
|
||||||
messagingService: MessagingServiceAbstraction;
|
messagingService: MessagingServiceAbstraction;
|
||||||
storageService: AbstractStorageService & ObservableStorageService;
|
storageService: BrowserLocalStorageService;
|
||||||
secureStorageService: AbstractStorageService;
|
secureStorageService: AbstractStorageService;
|
||||||
memoryStorageService: AbstractMemoryStorageService;
|
memoryStorageService: AbstractMemoryStorageService;
|
||||||
memoryStorageForStateProviders: AbstractMemoryStorageService & ObservableStorageService;
|
memoryStorageForStateProviders: AbstractMemoryStorageService & ObservableStorageService;
|
||||||
@ -1255,18 +1255,8 @@ export default class MainBackground {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStorage = (): Promise<any> =>
|
const storage = await this.storageService.getAll();
|
||||||
new Promise((resolve) => {
|
await this.storageService.clear();
|
||||||
chrome.storage.local.get(null, (o: any) => resolve(o));
|
|
||||||
});
|
|
||||||
|
|
||||||
const clearStorage = (): Promise<void> =>
|
|
||||||
new Promise((resolve) => {
|
|
||||||
chrome.storage.local.clear(() => resolve());
|
|
||||||
});
|
|
||||||
|
|
||||||
const storage = await getStorage();
|
|
||||||
await clearStorage();
|
|
||||||
|
|
||||||
for (const key in storage) {
|
for (const key in storage) {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
|
@ -10,6 +10,8 @@ import { fromChromeEvent } from "../../browser/from-chrome-event";
|
|||||||
|
|
||||||
export const serializationIndicator = "__json__";
|
export const serializationIndicator = "__json__";
|
||||||
|
|
||||||
|
type serializedObject = { [serializationIndicator]: true; value: string };
|
||||||
|
|
||||||
export const objToStore = (obj: any) => {
|
export const objToStore = (obj: any) => {
|
||||||
if (obj == null) {
|
if (obj == null) {
|
||||||
return null;
|
return null;
|
||||||
@ -61,11 +63,7 @@ export default abstract class AbstractChromeStorageService
|
|||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.chromeStorageApi.get(key, (obj: any) => {
|
this.chromeStorageApi.get(key, (obj: any) => {
|
||||||
if (obj != null && obj[key] != null) {
|
if (obj != null && obj[key] != null) {
|
||||||
let value = obj[key];
|
resolve(this.processGetObject(obj[key]));
|
||||||
if (value[serializationIndicator] && typeof value.value === "string") {
|
|
||||||
value = JSON.parse(value.value);
|
|
||||||
}
|
|
||||||
resolve(value as T);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
resolve(null);
|
resolve(null);
|
||||||
@ -95,4 +93,22 @@ export default abstract class AbstractChromeStorageService
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Backwards compatible resolution of retrieved object with new serialized storage */
|
||||||
|
protected processGetObject<T>(obj: T | serializedObject): T | null {
|
||||||
|
if (this.isSerialized(obj)) {
|
||||||
|
obj = JSON.parse(obj.value);
|
||||||
|
}
|
||||||
|
return obj as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Type guard for whether an object is tagged as serialized */
|
||||||
|
protected isSerialized<T>(value: T | serializedObject): value is serializedObject {
|
||||||
|
const asSerialized = value as serializedObject;
|
||||||
|
return (
|
||||||
|
asSerialized != null &&
|
||||||
|
asSerialized[serializationIndicator] &&
|
||||||
|
typeof asSerialized.value === "string"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,7 @@ describe("ChromeStorageApiService", () => {
|
|||||||
|
|
||||||
describe("get", () => {
|
describe("get", () => {
|
||||||
let getMock: jest.Mock;
|
let getMock: jest.Mock;
|
||||||
|
const key = "key";
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// setup get
|
// setup get
|
||||||
@ -76,7 +77,6 @@ describe("ChromeStorageApiService", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("returns a stored value when it is serialized", async () => {
|
it("returns a stored value when it is serialized", async () => {
|
||||||
const key = "key";
|
|
||||||
const value = { key: "value" };
|
const value = { key: "value" };
|
||||||
store[key] = objToStore(value);
|
store[key] = objToStore(value);
|
||||||
const result = await service.get(key);
|
const result = await service.get(key);
|
||||||
@ -84,7 +84,6 @@ describe("ChromeStorageApiService", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("returns a stored value when it is not serialized", async () => {
|
it("returns a stored value when it is not serialized", async () => {
|
||||||
const key = "key";
|
|
||||||
const value = "value";
|
const value = "value";
|
||||||
store[key] = value;
|
store[key] = value;
|
||||||
const result = await service.get(key);
|
const result = await service.get(key);
|
||||||
@ -95,5 +94,12 @@ describe("ChromeStorageApiService", () => {
|
|||||||
const result = await service.get("key");
|
const result = await service.get("key");
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("returns null when the stored object is null", async () => {
|
||||||
|
store[key] = null;
|
||||||
|
|
||||||
|
const result = await service.get(key);
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
import { objToStore } from "./abstractions/abstract-chrome-storage-api.service";
|
||||||
|
import BrowserLocalStorageService from "./browser-local-storage.service";
|
||||||
|
|
||||||
|
describe("BrowserLocalStorageService", () => {
|
||||||
|
let service: BrowserLocalStorageService;
|
||||||
|
let store: Record<any, any>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
store = {};
|
||||||
|
|
||||||
|
service = new BrowserLocalStorageService();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("clear", () => {
|
||||||
|
let clearMock: jest.Mock;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
clearMock = chrome.storage.local.clear as jest.Mock;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses the api to clear", async () => {
|
||||||
|
await service.clear();
|
||||||
|
|
||||||
|
expect(clearMock).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getAll", () => {
|
||||||
|
let getMock: jest.Mock;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// setup get
|
||||||
|
getMock = chrome.storage.local.get as jest.Mock;
|
||||||
|
getMock.mockImplementation((key, callback) => {
|
||||||
|
if (key == null) {
|
||||||
|
callback(store);
|
||||||
|
} else {
|
||||||
|
callback({ [key]: store[key] });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns all values", async () => {
|
||||||
|
store["key1"] = "string";
|
||||||
|
store["key2"] = 0;
|
||||||
|
const result = await service.getAll();
|
||||||
|
|
||||||
|
expect(result).toEqual(store);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles empty stores", async () => {
|
||||||
|
const result = await service.getAll();
|
||||||
|
|
||||||
|
expect(result).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles stores with null values", async () => {
|
||||||
|
store["key"] = null;
|
||||||
|
|
||||||
|
const result = await service.getAll();
|
||||||
|
expect(result).toEqual(store);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles values processed for storage", async () => {
|
||||||
|
const obj = { test: 2 };
|
||||||
|
const key = "key";
|
||||||
|
store[key] = objToStore(obj);
|
||||||
|
|
||||||
|
const result = await service.getAll();
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
[key]: obj,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// This is a test of backwards compatibility before local storage was serialized.
|
||||||
|
it("handles values that were stored without processing for storage", async () => {
|
||||||
|
const obj = { test: 2 };
|
||||||
|
const key = "key";
|
||||||
|
store[key] = obj;
|
||||||
|
|
||||||
|
const result = await service.getAll();
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
[key]: obj,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -4,4 +4,32 @@ export default class BrowserLocalStorageService extends AbstractChromeStorageSer
|
|||||||
constructor() {
|
constructor() {
|
||||||
super(chrome.storage.local);
|
super(chrome.storage.local);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears local storage
|
||||||
|
*/
|
||||||
|
async clear() {
|
||||||
|
await chrome.storage.local.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves all objects stored in local storage.
|
||||||
|
*
|
||||||
|
* @remarks This method processes values prior to resolving, do not use `chrome.storage.local` directly
|
||||||
|
* @returns Promise resolving to keyed object of all stored data
|
||||||
|
*/
|
||||||
|
async getAll(): Promise<Record<string, unknown>> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.chromeStorageApi.get(null, (allStorage) => {
|
||||||
|
const resolved = Object.entries(allStorage).reduce(
|
||||||
|
(agg, [key, value]) => {
|
||||||
|
agg[key] = this.processGetObject(value);
|
||||||
|
return agg;
|
||||||
|
},
|
||||||
|
{} as Record<string, unknown>,
|
||||||
|
);
|
||||||
|
resolve(resolved);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user