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:
committed by
Facebook Github Bot
parent
eb64ff0832
commit
7775e82851
1
src/utils/__tests__/data/settings-v1-valid.json
Normal file
1
src/utils/__tests__/data/settings-v1-valid.json
Normal file
@@ -0,0 +1 @@
|
||||
{"androidHome":"/opt/android_sdk","something":{"else":4},"_persist":{"version":-1,"rehydrated":true}}
|
||||
37
src/utils/__tests__/jsonFileStorage.node.js
Normal file
37
src/utils/__tests__/jsonFileStorage.node.js
Normal 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);
|
||||
});
|
||||
89
src/utils/jsonFileReduxPersistStorage.tsx
Normal file
89
src/utils/jsonFileReduxPersistStorage.tsx
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user