[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";
|
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
|
export default abstract class AbstractChromeStorageService
|
||||||
implements AbstractStorageService, ObservableStorageService
|
implements AbstractStorageService, ObservableStorageService
|
||||||
{
|
{
|
||||||
|
@ -44,7 +61,11 @@ 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) {
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
resolve(null);
|
resolve(null);
|
||||||
|
@ -57,14 +78,7 @@ export default abstract class AbstractChromeStorageService
|
||||||
}
|
}
|
||||||
|
|
||||||
async save(key: string, obj: any): Promise<void> {
|
async save(key: string, obj: any): Promise<void> {
|
||||||
if (obj == null) {
|
obj = objToStore(obj);
|
||||||
// Fix safari not liking null in set
|
|
||||||
return this.remove(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj instanceof Set) {
|
|
||||||
obj = Array.from(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
const keyedObj = { [key]: obj };
|
const keyedObj = { [key]: obj };
|
||||||
return new Promise<void>((resolve) => {
|
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