[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:
Matt Gibson 2024-04-08 10:41:45 -05:00 committed by GitHub
parent 5c30be2927
commit d1a0a20daa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 122 additions and 9 deletions

View File

@ -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) => {

View File

@ -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();
});
});
});