Add JsonFileStorage for redux-persist

Summary:
This will be used for the settings file.
It results in normal JSON, as opposed to json where the value of every key is an escaped string.

Reviewed By: passy

Differential Revision: D17712688

fbshipit-source-id: d37ed93707c7352719fa72a05bf51953611f52c0
This commit is contained in:
John Knox
2019-10-07 08:49:05 -07:00
committed by Facebook Github Bot
parent eb64ff0832
commit 7775e82851
3 changed files with 127 additions and 0 deletions

View File

@@ -0,0 +1 @@
{"androidHome":"/opt/android_sdk","something":{"else":4},"_persist":{"version":-1,"rehydrated":true}}

View File

@@ -0,0 +1,37 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
import JsonFileStorage from '../jsonFileReduxPersistStorage.tsx';
import fs from 'fs';
const validSerializedData = fs
.readFileSync('src/utils/__tests__/data/settings-v1-valid.json')
.toString()
.trim();
const validDeserializedData =
'{"androidHome":"\\"/opt/android_sdk\\"","something":"{\\"else\\":4}","_persist":"{\\"version\\":-1,\\"rehydrated\\":true}"}';
const storage = new JsonFileStorage(
'src/utils/__tests__/data/settings-v1-valid.json',
);
test('A valid settings file gets parsed correctly', () => {
return storage
.getItem('anykey')
.then(result => expect(result).toEqual(validDeserializedData));
});
test('deserialize works as expected', () => {
const deserialized = storage.deserializeValue(validSerializedData);
expect(deserialized).toEqual(validDeserializedData);
});
test('serialize works as expected', () => {
const serialized = storage.serializeValue(validDeserializedData);
expect(serialized).toEqual(validSerializedData);
});

View File

@@ -0,0 +1,89 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
import {promises} from 'fs';
/**
* Redux-persist storage engine for storing state in a human readable JSON file.
*
* Differs from the usual engines in two ways:
* * The key is ignored. This storage will only hold one key, so each setItem() call will overwrite the previous one.
* * Stored files are "human readable". Redux-persist calls storage engines with preserialized values that contain escaped strings inside json.
* This engine re-serializes them by parsing the inner strings to store them as top-level json.
* Transforms haven't been used because they operate before serialization, so all serialized values would still end up as strings.
*/
export default class JsonFileStorage {
filepath: string;
constructor(filepath: string) {
this.filepath = filepath;
}
private parseFile(): Promise<any> {
return promises
.readFile(this.filepath)
.then(buffer => buffer.toString())
.then(this.deserializeValue)
.catch(e => {
console.error(
`Failed to read settings file: "${
this.filepath
}". ${e}. Replacing file with default settings.`,
);
return promises
.writeFile(this.filepath, JSON.stringify({}))
.then(() => ({}));
});
}
getItem(_key: string, callback?: (_: any) => any): Promise<any> {
const promise = this.parseFile();
callback && promise.then(callback);
return promise;
}
// Sets a new value and returns the value that was PREVIOUSLY set.
// This mirrors the behaviour of the localForage storage engine.
// Not thread-safe.
setItem(_key: string, value: any, callback?: (_: any) => any): Promise<any> {
const originalValue = this.parseFile();
const writePromise = originalValue.then(_ =>
promises.writeFile(this.filepath, this.serializeValue(value)),
);
return Promise.all([originalValue, writePromise]).then(([o, _]) => {
callback && callback(o);
return o;
});
}
removeItem(_key: string, callback?: () => any): Promise<void> {
return promises
.writeFile(this.filepath, JSON.stringify({}))
.then(_ => callback && callback())
.then(() => {});
}
serializeValue(value: string): string {
const reconstructedObject = Object.entries(JSON.parse(value))
.map(([k, v]: [string, unknown]) => [k, JSON.parse(v as string)])
.reduce((acc: {[key: string]: any}, cv) => {
acc[cv[0]] = cv[1];
return acc;
}, {});
return JSON.stringify(reconstructedObject);
}
deserializeValue(value: string): string {
const reconstructedObject = Object.entries(JSON.parse(value))
.map(([k, v]: [string, unknown]) => [k, JSON.stringify(v)])
.reduce((acc: {[key: string]: string}, cv) => {
acc[cv[0]] = cv[1];
return acc;
}, {});
return JSON.stringify(reconstructedObject);
}
}