mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-24 12:06:15 +01:00
[PM-7341] Force serialization of data in chrome storage api (#8621)
* Force serialization of data in chrome storage api * Test chrome api storage serialization * Update apps/browser/src/platform/services/abstractions/abstract-chrome-storage-api.service.ts Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com> --------- Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>
This commit is contained in:
parent
5c30be2927
commit
d1a0a20daa
@ -8,6 +8,23 @@ import {
|
||||
|
||||
import { fromChromeEvent } from "../../browser/from-chrome-event";
|
||||
|
||||
export const serializationIndicator = "__json__";
|
||||
|
||||
export const objToStore = (obj: any) => {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (obj instanceof Set) {
|
||||
obj = Array.from(obj);
|
||||
}
|
||||
|
||||
return {
|
||||
[serializationIndicator]: true,
|
||||
value: JSON.stringify(obj),
|
||||
};
|
||||
};
|
||||
|
||||
export default abstract class AbstractChromeStorageService
|
||||
implements AbstractStorageService, ObservableStorageService
|
||||
{
|
||||
@ -44,7 +61,11 @@ export default abstract class AbstractChromeStorageService
|
||||
return new Promise((resolve) => {
|
||||
this.chromeStorageApi.get(key, (obj: any) => {
|
||||
if (obj != null && obj[key] != null) {
|
||||
resolve(obj[key] as T);
|
||||
let value = obj[key];
|
||||
if (value[serializationIndicator] && typeof value.value === "string") {
|
||||
value = JSON.parse(value.value);
|
||||
}
|
||||
resolve(value as T);
|
||||
return;
|
||||
}
|
||||
resolve(null);
|
||||
@ -57,14 +78,7 @@ export default abstract class AbstractChromeStorageService
|
||||
}
|
||||
|
||||
async save(key: string, obj: any): Promise<void> {
|
||||
if (obj == null) {
|
||||
// Fix safari not liking null in set
|
||||
return this.remove(key);
|
||||
}
|
||||
|
||||
if (obj instanceof Set) {
|
||||
obj = Array.from(obj);
|
||||
}
|
||||
obj = objToStore(obj);
|
||||
|
||||
const keyedObj = { [key]: obj };
|
||||
return new Promise<void>((resolve) => {
|
||||
|
@ -0,0 +1,99 @@
|
||||
import AbstractChromeStorageService, {
|
||||
objToStore,
|
||||
serializationIndicator,
|
||||
} from "./abstract-chrome-storage-api.service";
|
||||
|
||||
class TestChromeStorageApiService extends AbstractChromeStorageService {}
|
||||
|
||||
describe("objectToStore", () => {
|
||||
it("converts an object to a tagged string", () => {
|
||||
const obj = { key: "value" };
|
||||
const result = objToStore(obj);
|
||||
expect(result).toEqual({
|
||||
[serializationIndicator]: true,
|
||||
value: JSON.stringify(obj),
|
||||
});
|
||||
});
|
||||
|
||||
it("converts a set to an array prior to serialization", () => {
|
||||
const obj = new Set(["value"]);
|
||||
const result = objToStore(obj);
|
||||
expect(result).toEqual({
|
||||
[serializationIndicator]: true,
|
||||
value: JSON.stringify(Array.from(obj)),
|
||||
});
|
||||
});
|
||||
|
||||
it("does nothing to null", () => {
|
||||
expect(objToStore(null)).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ChromeStorageApiService", () => {
|
||||
let service: TestChromeStorageApiService;
|
||||
let store: Record<any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
store = {};
|
||||
|
||||
service = new TestChromeStorageApiService(chrome.storage.local);
|
||||
});
|
||||
|
||||
describe("save", () => {
|
||||
let setMock: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
// setup save
|
||||
setMock = chrome.storage.local.set as jest.Mock;
|
||||
setMock.mockImplementation((data, callback) => {
|
||||
Object.assign(store, data);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
|
||||
it("uses `objToStore` to prepare a value for set", async () => {
|
||||
const key = "key";
|
||||
const value = { key: "value" };
|
||||
await service.save(key, value);
|
||||
expect(setMock).toHaveBeenCalledWith(
|
||||
{
|
||||
[key]: objToStore(value),
|
||||
},
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("get", () => {
|
||||
let getMock: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
// setup get
|
||||
getMock = chrome.storage.local.get as jest.Mock;
|
||||
getMock.mockImplementation((key, callback) => {
|
||||
callback({ [key]: store[key] });
|
||||
});
|
||||
});
|
||||
|
||||
it("returns a stored value when it is serialized", async () => {
|
||||
const key = "key";
|
||||
const value = { key: "value" };
|
||||
store[key] = objToStore(value);
|
||||
const result = await service.get(key);
|
||||
expect(result).toEqual(value);
|
||||
});
|
||||
|
||||
it("returns a stored value when it is not serialized", async () => {
|
||||
const key = "key";
|
||||
const value = "value";
|
||||
store[key] = value;
|
||||
const result = await service.get(key);
|
||||
expect(result).toEqual(value);
|
||||
});
|
||||
|
||||
it("returns null when the key does not exist", async () => {
|
||||
const result = await service.get("key");
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user