How to securely save the redux-persist-transform-encrypt key? - react-native

i am trying to persist and encrypt the redux store my react-native app.
i am trying to use redux-persist-transform-encrypt as per the documentation:
import { persistReducer } from 'redux-persist'
import createEncryptor from 'redux-persist-transform-encrypt'
const encryptor = createEncryptor({
secretKey: 'my-super-secret-key'
})
const reducer = persistReducer(
{
transforms: [encryptor]
},
baseReducer
)
but the big deal is to find a secure way to store 'my-super-secret-key'.
I've successfully got it from user input and saved it with react-native-keychain.
Now the problem is that the function to get the key from the keychain is asynchronous and i would need to get the key before the store initialisation.
The result would look like this
import { persistReducer } from 'redux-persist'
import createEncryptor from 'redux-persist-transform-encrypt'
const encryptionKey = // get the key here from the keychain before initiating the store
const encryptor = createEncryptor({
secretKey: encryptionKey
})
const reducer = persistReducer(
{
transforms: [encryptor]
},
baseReducer
)
Do someone have any workaround for me?

I also spent some time how to resolve this. At first I found nice library to save generated secret key into keychain called react-native-sensitive-info. After that I was facing the problem that getting and setting generated secret key is asynchronous. Then I figured out that AsyncStorage is used as a storage in the config for persistReducer and there was the way - let's create a storage which uses react-native-sensitive-info. After a while, I stumbled upon a library that already exists and is implementing exactly the same logic. This is it -> redux-persist-sensitive-storage.
Just use it like this:
...
import createSensitiveStorage from 'redux-persist-sensitive-storage';
const appReducer = combineReducers(myReducers);
const persistConfig = {
key: 'root',
storage: createSensitiveStorage({
keychainService: 'myKeychain',
sharedPreferencesName: 'mySharedPrefs'
})
}
const persistedReducer = persistReducer(persistConfig, appReducer);
const store = createStore(persistedReducer, applyMiddleware(thunkMiddleware));
const persistor = persistStore(store);
...
That's it!

Related

React-Native retrieve API_URL from AsyncStorage and make in accessible in all app (for test purpose)

I am trying to retrieve the API_URL from AsyncStorage and make it accessible in all app, the storing and retrieving (in settings screen) is working fine but when I try to load the data in the App.js using useEffect hook, it returns null. Reloading the app is not working but as soon as I save the App.js (using CTRL-S) it works fine.
Please let me know the correct way to do this.
import React, { useEffect, useState } from "react";
import AsyncStorage from "#react-native-async-storage/async-storage";
export default function App() {
const [hostState, setHostState] = useState(null);
const getAHostInfoAsync = async () => {
const hostInfo = AsyncStorage.getItem('host').then(
setHostState(hostInfo)
).then(
console.log(hostState)
);
};
useEffect(() => {
getAHostInfoAsync();
}, []);
module.exports = {
host: hostState
};
}
and using in another file:
import App from "../../../App";
const API_URL = App.host;
I think your issue is in the way you use async/then. instead of async await.
I am not 100% sure that this is your issue. But if I change my async/await function to use async/then the way you are having it, my IDE says that the variable (hostInfo) might not have been initialised. In any case, I think this is a better syntax than with then.
const getAHostInfoAsync = async () => {
const hostInfo = await AsyncStorage.getItem('host')
setHostState(hostInfo)
console.log(hostState)
};

Storing in IndexedDB using Pinia

Is it possible to store data locally in IndexedDB using Pinia ?
I tried using Pinia persisted state and it stores data locally in LocalStorage as default. I just want to try if it will work with IndexedDB since it has a larger size capacity.
You can implement your own pinia plugin to use whatever storage you want.
Here is an example using localForage.
import { createApp } from 'vue'
import { createPinia, type Store } from 'pinia'
import App from './App.vue'
import localForage from "localforage";
const app = createApp(App)
// Optional
localForage.config({
driver: localForage.INDEXEDDB, // This force IndexedDB as the driver
})
async function indexDbPlugin({ store }: { store: Store }) {
const stored = await localForage.getItem(store.$id + '-state')
if (stored) {
store.$patch(stored)
}
store.$subscribe(() => {
localForage
.setItem(store.$id + '-state', { ...store.$state }) // Destructure to transform to plain object
})
}
const pinia = createPinia()
pinia.use(indexDbPlugin)
app.use(pinia)
app.mount('#app')
https://pinia.vuejs.org/core-concepts/plugins.html#introduction
But with the plugin pinia-plugin-persistedstate you cannot use indexDb as it is asynchronous and this plugin only supports synchronous storage:
https://prazdevs.github.io/pinia-plugin-persistedstate/guide/limitations.html#storage-must-be-synchronous

Can Redux store token sessions in React Native?

I am new to Redux and React Native and would like know if I can implement and store token sessions in Redux for keeping the user logged in after they close and reopen the app. I have found out some people recommend AsyncStorage but my app state is handled with Redux.
This is my Redux store which uses AsyncStorage too.
import { createStore } from 'redux'
import { persistStore, persistReducer } from 'redux-persist'
import AsyncStorage from '#react-native-community/async-storage';
import reducer from './reducers/index'
const persistConfig = {
key: 'root',
version: 0,
storage: AsyncStorage
}
const persistedReducer = persistReducer(persistConfig, reducer)
const store = createStore(persistedReducer)
const persistor = persistStore(store)
export { store, persistor }
Would that be enough to keep a token session as I store other data in the same way?
redux store cannot restore data after closing and reopening the app.
import { AsyncStorage } from 'react-native'
you can store by
AsyncStorage.setItem(userSessionKey, userData)
and restore by
async function restoreSession() {
try {
const data = await AsyncStorage.getItem(userSessionKey)
const userData = JSON.parse(data)
if (userData !== null) {
return userData
} else {
throw new Error('User Data is empty')
}
} catch (error) {
//console.log(error)
return null
}
}
so, when the app starts, before navigating to main app,
restore the data, and add to redux store
Redux can't persist/keep the data of the store/reducer when you kill the application.
But with the help of redux-persist library, redux can persist the data of the reducer/store. Also if you user reudx-persist you don't have to manually create AsyncStorage calls for retrieving initially when app starts redux-persist will handle that for you. You can use different storage engines not just AsyncStorage more info here
In your case you can totally store user session token in redux with the help of redux-persist. Use Whitelist/blacklist in persist config to let redux-persist know which reducer to persist.
e.g.
// BLACKLIST
const persistConfig = {
key: 'root',
storage: storage,
blacklist: ['authReducer'] // navigation will not be persisted
};
// WHITELIST
const persistConfig = {
key: 'root',
storage: storage,
whitelist: ['authReducer'] // only navigation will be persisted
};

redux-persist/createPersistoid: error serializing state TypeError: Converting circular structure to JSON

I am trying to integrate redux-persist to my react-native project. The aim is to persist the redux store's data between application re-launch so that user does not need to log in each time they launch the app. Also, I want to store the previously viewed data on the local storage to avoid re-querying all the data each time app is re-launched.
I added the redux persist support as per the code below:
import { AsyncStorage } from 'react-native';
import { createStore, applyMiddleware } from 'redux';
//import { createLogger } from 'redux-logger';
import { persistStore, persistReducer } from 'redux-persist';
import rootReducer from '../reducers/index';
import ReduxThunk from 'redux-thunk';
import { createTransform } from 'redux-persist';
import JSOG from 'jsog';
export const JSOGTransform = createTransform(
(inboundState, key) => JSOG.encode(inboundState),
(outboundState, key) => JSOG.decode(outboundState),
)
const persistConfig = {
// Root
key: 'root',
// Storage Method (React Native)
storage: AsyncStorage,
//transforms: [JSOGTransform]
// Whitelist (Save Specific Reducers)
// whitelist: [
// 'authReducer',
// ],
// // Blacklist (Don't Save Specific Reducers)
// blacklist: [
// 'counterReducer',
// ],
};
// Middleware: Redux Persist Persisted Reducer
const persistedReducer = persistReducer(persistConfig, rootReducer);
// Redux: Store
const store = createStore(
persistedReducer,
applyMiddleware(ReduxThunk),
);
// Middleware: Redux Persist Persister
let persistor = persistStore(store);
// Exports
export {
store,
persistor,
};
If I don't add jsog and use the transforms: [JSOGTransform] line in the persistConfig I get this error:
redux-persist/createPersistoid: error serializing state TypeError:
Converting circular structure to JSON
If I uncomment the 'transforms' line in the persistConfig (as per the suggestion here: https://github.com/rt2zz/redux-persist/issues/735), then I get this error:
Exception in HostObject::set: < unknown >
I am just persisting the returned "user" object from the firestore database in my redux-store. Without the redux-persist, there has been no problem, but with the persist added I am having this issue.
What type of circular problem would exist in the user object returned after successful login to firestore (using password/email auth.)?
Why would JSOG not work as suggested in the link above? Any alternatives to how I can solve this problem?
P.S. Not only the user returned from firestore is causing these errors, but any data returned from firestore seems to fail to persist.
I appreciate any helps!
Cheers...
I think I solved the problem. Instead of JSOG I installed flatted and used in for my redux-persist transform.
Working transform and persistConfig looks like this:
export const transformCircular = createTransform(
(inboundState, key) => Flatted.stringify(inboundState),
(outboundState, key) => Flatted.parse(outboundState),
)
const persistConfig = {
key: 'root',
storage: AsyncStorage,
stateReconciler: autoMergeLevel2,
transforms: [transformCircular]
};
Some developer have this problem because of the socket set in Redax, Otherwise, you can use this way as well
// SetTransform.js
import { createTransform } from 'redux-persist';
const SetTransform = createTransform(
// transform state on its way to being serialized and persisted.
(inboundState, key) => {
// convert mySet to an Array.
return { ...inboundState, mySet: [...inboundState.mySet] };
},
// transform state being rehydrated
(outboundState, key) => {
// convert mySet back to a Set.
return { ...outboundState, mySet: new Set(outboundState.mySet) };
},
// define which reducers this transform gets called for.
{ whitelist: ['someReducer'] }
);
export default SetTransform;
and
// PersistReducers
import storage from 'redux-persist/lib/storage';
import { SetTransform } from './transforms';
const persistConfig = {
key: 'root',
storage: storage,
transforms: [SetTransform]
};

How to Persist Mobx State Tree in React Native?

I need to persist a MST Store in React Native. The data is changed seldomly.
I'm confused between using AsyncStorage and AutoRun.
For persisting MST stores, you might be interested in using mst-persist, which, per the README, is currently a small wrapper around MST's onSnapshot and applySnapshot (disclaimer: I'm the creator).
To persist data in React Native with mst-persist backed by AsyncStorage, one would do:
import { types } from 'mobx-state-tree'
import { AsyncStorage } from 'react-native'
import { persist } from 'mst-persist'
const SomeStore = types.model('Store', {
name: 'John Doe',
age: 32
})
const someStore = SomeStore.create()
persist('some', someStore, {
storage: AsyncStorage, // default: localStorage
jsonify: true // if you use AsyncStorage, this should be true
// default: true
whitelist: ['name'] // only these keys will be persisted
}).then(() => console.log('someStore has been hydrated'))
My original use case for mst-persist was for React Native and the current README will actually point you to a commit in an OSS RN manga reader app I made as an example.
If you're interested in how to do it with MST without another library like mst-persist, the persistence source code is actually < 50 LoC currently. And minus some features, it's a brisk < 20 LoC:
import { onSnapshot, applySnapshot } from 'mobx-state-tree'
export const persist = (name, store, options = {}) => {
let {storage, jsonify} = options
onSnapshot(store, (_snapshot) => {
const snapshot = { ..._snapshot }
const data = !jsonify ? snapshot : JSON.stringify(snapshot)
storage.setItem(name, data)
})
return storage.getItem(name)
.then((data) => {
const snapshot = !jsonify ? data : JSON.parse(data)
applySnapshot(store, snapshot)
})
}
There are a handful of other examples out in the wild that show similar functionality as well, such as this gist that mst-persist is partly inspired by, this repo that uses HoCs and PersistGates similar to redux-persist, and this gist that takes multiple stores as an argument.