Skip to content
This repository was archived by the owner on Oct 16, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
module.exports = {
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 2015,
sourceType: "module",
},
extends: [
"zeal",
"zeal/react",
"zeal/react-native",
"prettier",
"prettier/react"
"plugin:@typescript-eslint/recommended",
"prettier/@typescript-eslint",
"plugin:prettier/recommended"
],
root: true,
rules: {
"callback-return": "off"
"@typescript-eslint/explicit-module-boundary-types": "off"
}
};
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
yarn.lock

# Runtime data
pids
Expand Down
7 changes: 7 additions & 0 deletions .prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
semi: true,
trailingComma: "es5",
singleQuote: false,
printWidth: 80,
tabWidth: 2
};
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,21 @@ Storage engine to use [react-native-sensitive-info](https://github.com/mCodex/re

react-native-sensitive-info manages all data stored in Android Shared Preferences and iOS Keychain.

**NOTE:** Android Shared Preferences are not secure, but there is [a branch of react-native-sensitive-info](https://github.com/mCodex/react-native-sensitive-info/tree/keystore) that uses the Android keystore instead of shared preferences. You can use that branch with redux-persist-sensitive-storage if you prefer.
**NOTE:** Android Shared Preferences are not secure, but there is [a branch of react-native-sensitive-info](https://github.com/mCodex/react-native-sensitive-info/tree/keystore) or v6.x that uses the Android keystore instead of shared preferences. You can use that branch or v6.x with redux-persist-sensitive-storage if you prefer.

## Installation

You can install this package using either `yarn` or `npm`. You will also need to install and link [react-native-sensitive-info](https://github.com/mCodex/react-native-sensitive-info).
You can install this package using either `yarn` or `npm`. You will also need to install and link [react-native-sensitive-info](https://github.com/mCodex/react-native-sensitive-info).

Using Yarn:

```
yarn add redux-persist-sensitive-storage react-native-sensitive-info
react-native link react-native-sensitive-info
```

Using npm:

```
npm install --save redux-persist-sensitive-storage react-native-sensitive-info
react-native link react-native-sensitive-info
Expand All @@ -31,7 +33,7 @@ react-native link react-native-sensitive-info
To use redux-persist-sensitive-storage, create a sensitive storage instance using `createSensitiveStorage` and then
configure redux-persist according to [its documentation](https://github.com/rt2zz/redux-persist#redux-persist) using your instance as the storage argument in the configuration.

`createSensitiveStorage` takes an optional set of configuration options. These are used to configure the keychain service (iOS) and shared preferences name (Android) that react-native-sensitive-info uses. See [their documentation](https://github.com/mCodex/react-native-sensitive-info#methods) for more information.
`createSensitiveStorage` takes an optional set of configuration options. These are used to configure the keychain service (iOS) and shared preferences name (Android) that react-native-sensitive-info uses. See [their documentation](https://github.com/mCodex/react-native-sensitive-info#methods) for more information.

### For redux-persist v5.x or later

Expand All @@ -43,7 +45,7 @@ import reducers from "./reducers"; // where reducers is an object of reducers

const storage = createSensitiveStorage({
keychainService: "myKeychain",
sharedPreferencesName: "mySharedPrefs"
sharedPreferencesName: "mySharedPrefs",
});

const config = {
Expand All @@ -53,7 +55,7 @@ const config = {

const reducer = persistCombineReducers(config, reducers);

function configureStore () {
function configureStore() {
// ...
let store = createStore(reducer);
let persistor = persistStore(store);
Expand All @@ -62,7 +64,7 @@ function configureStore () {
}
```

You may want to only persist some keys in secure storage, and persist other parts of your state in local storage. If that's the case, you can use redux-persist's [Nested Persists](https://github.com/rt2zz/redux-persist#nested-persists) support. Your configuration might look something like this:
You may want to only persist some keys in secure storage, and persist other parts of your state in local storage. If that's the case, you can use redux-persist's [Nested Persists](https://github.com/rt2zz/redux-persist#nested-persists) support. Your configuration might look something like this:

```js
import { AsyncStorage } from "react-native";
Expand All @@ -74,23 +76,23 @@ import { mainReducer, tokenReducer } from "./reducers";

const sensitiveStorage = createSensitiveStorage({
keychainService: "myKeychain",
sharedPreferencesName: "mySharedPrefs"
sharedPreferencesName: "mySharedPrefs",
});

const mainPersistConfig = {
key: "main",
storage: AsyncStorage,
blacklist: ["someEphemeralKey"]
blacklist: ["someEphemeralKey"],
};

const tokenPersistConfig = {
key: "token",
storage: sensitiveStorage
storage: sensitiveStorage,
};

let rootReducer = combineReducers({
main: persistReducer(mainPersistConfig, mainReducer),
token: persistReducer(tokenPersistConfig, tokenReducer)
token: persistReducer(tokenPersistConfig, tokenReducer),
});
```

Expand Down
35 changes: 35 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import sensitiveInfo from "react-native-sensitive-info";
export default function (
options?: sensitiveInfo.RNSensitiveInfoOptions
): {
getItem(
key: string,
callback?: {
(_: null, result: string[]): void;
(_: null, result: string | null): void;
(error: unknown): void;
}
): Promise<string | null>;
setItem(
key: string,
value: string,
callback?: {
(_: null, result: string[]): void;
(_: null, result: string | null): void;
(error: unknown): void;
}
): Promise<void>;
removeItem(
key: string,
callback?: {
(_: null, result: string[]): void;
(_: null, result: string | null): void;
(error: unknown): void;
}
): Promise<void>;
getAllKeys(callback?: {
(_: null, result: string[]): void;
(_: null, result: string | null): void;
(error: unknown): void;
}): Promise<string[]>;
};
192 changes: 106 additions & 86 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,88 +1,108 @@
import { Platform } from "react-native";
import sensitiveInfo from "react-native-sensitive-info";

export default function(options = {}) {
// react-native-sensitive-info returns different a different structure on iOS
// than it does on Android.
//
// iOS:
// [
// [
// { service: 'app', key: 'foo', value: 'bar' },
// { service: 'app', key: 'baz', value: 'quux' }
// ]
// ]
//
// Android:
// {
// foo: 'bar',
// baz: 'quux'
// }
//
// See https://github.com/mCodex/react-native-sensitive-info/issues/8
//
// `extractKeys` adapts for the different structure to return the list of
// keys.
const extractKeys = Platform.select({
ios: items => items[0].map(item => item.key),
android: Object.keys
});

const noop = () => null;

return {
async getItem(key, callback = noop) {
try {
// getItem() returns `null` on Android and `undefined` on iOS;
// explicitly return `null` here as `undefined` causes an exception
// upstream.
let result = await sensitiveInfo.getItem(key, options);

if (typeof result === "undefined") {
result = null;
}

callback(null, result);

return result;
} catch (error) {
callback(error);
throw error;
}
},

async setItem(key, value, callback = noop) {
try {
await sensitiveInfo.setItem(key, value, options);
callback(null);
} catch (error) {
callback(error);
throw error;
}
},

async removeItem(key, callback = noop) {
try {
await sensitiveInfo.deleteItem(key, options);
callback(null);
} catch (error) {
callback(error);
throw error;
}
},

async getAllKeys(callback = noop) {
try {
const values = await sensitiveInfo.getAllItems(options);
const result = extractKeys(values);

callback(null, result);

return result;
} catch (error) {
callback(error);
throw error;
}
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const react_native_1 = require("react-native");
const react_native_sensitive_info_1 = __importDefault(require("react-native-sensitive-info"));
function default_1(options = {}) {
// react-native-sensitive-info returns different a different structure on iOS
// than it does on Android.
//
// iOS:
// [
// [
// { service: 'app', key: 'foo', value: 'bar' },
// { service: 'app', key: 'baz', value: 'quux' }
// ]
// ]
//
// Android:
// {
// foo: 'bar',
// baz: 'quux'
// }
//
// See https://github.com/mCodex/react-native-sensitive-info/issues/8
//
// `extractKeys` adapts for the different structure to return the list of
// keys.
const extractKeys = react_native_1.Platform.select({
android: Object.keys,
ios: (items) => items[0].map((item) => item.key),
});
function noop() {
return null;
}
};
return {
getItem(key, callback = noop) {
return __awaiter(this, void 0, void 0, function* () {
try {
// getItem() returns `null` on Android and `undefined` on iOS;
// explicitly return `null` here as `undefined` causes an exception
// upstream.
let result = yield react_native_sensitive_info_1.default.getItem(key, options);
if (typeof result === "undefined") {
result = null;
}
callback(null, result);
return result;
}
catch (error) {
callback(error);
throw error;
}
});
},
setItem(key, value, callback = noop) {
return __awaiter(this, void 0, void 0, function* () {
try {
yield react_native_sensitive_info_1.default.setItem(key, value, options);
callback(null);
}
catch (error) {
callback(error);
throw error;
}
});
},
removeItem(key, callback = noop) {
return __awaiter(this, void 0, void 0, function* () {
try {
yield react_native_sensitive_info_1.default.deleteItem(key, options);
callback(null);
}
catch (error) {
callback(error);
throw error;
}
});
},
getAllKeys(callback = noop) {
return __awaiter(this, void 0, void 0, function* () {
try {
const values = yield react_native_sensitive_info_1.default.getAllItems(options);
if (typeof extractKeys === "undefined")
throw new Error("Platform not supported");
const result = extractKeys(values);
callback(null, result);
return result;
}
catch (error) {
callback(error);
throw error;
}
});
},
};
}
exports.default = default_1;
Loading