Skip to content
This repository has been archived by the owner on Nov 7, 2023. It is now read-only.

Version 2.0.0 #5

Merged
merged 15 commits into from
Feb 15, 2020
Merged
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,6 @@ buck-out/
\.buckd/
*.keystore
.vscode/settings.json

# JEST
coverage/
39 changes: 20 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,16 @@ import EncryptedStorage from 'react-native-encrypted-storage';

```js
const storeUserSession = async () => {
const success = await EncryptedStorage.store('user_session', {
username : 'emeraldsanto',
age : 21,
languages : ['fr', 'en', 'de'],
token : 'ACCESS_TOKEN'
});

if (success === true) {
try {
await EncryptedStorage.setItem('user_session', {
username : 'emeraldsanto',
age : 21,
languages : ['fr', 'en', 'de'],
token : 'ACCESS_TOKEN'
});

// Congrats! You've just stored your first value!
}

else {
} catch (error) {
// There was an error on the native side
}
}
Expand All @@ -72,10 +70,14 @@ const storeUserSession = async () => {

```js
const retrieveUserSession = async () => {
const session = await EncryptedStorage.retrieve("user_session");
try {
const session = await EncryptedStorage.getItem("user_session");

if (session !== undefined) {
// Congrats! You've just retrieved your first value!
if (session !== null) {
// Congrats! You've just retrieved your first value!
}
} catch (error) {
// There was an error on the native side
}
}
```
Expand All @@ -84,12 +86,11 @@ const retrieveUserSession = async () => {

```js
const removeUserSession = async () => {
const success = await EncryptedStorage.remove('user_session');

if (success === true) {
try {
await EncryptedStorage.removeItem('user_session');
// Congrats! You've just removed your first value!
const previousValue = await EncryptedStorage.retrieve('user_session');
console.log(previousValue); // undefined
} catch (error) {
// There was an error on the native side
}
}
```
Expand Down
3 changes: 2 additions & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import groovy.json.JsonSlurper

def DEFAULT_COMPILE_SDK_VERSION = 28
def DEFAULT_BUILD_TOOLS_VERSION = '28.0.3'
def DEFAULT_MIN_SDK_VERSION = 16
def DEFAULT_MIN_SDK_VERSION = 23
def DEFAULT_TARGET_SDK_VERSION = 28

def safeExtGet(prop, fallback) {
Expand Down Expand Up @@ -86,6 +86,7 @@ dependencies {
//noinspection GradleDynamicVersion
implementation 'com.facebook.react:react-native:+'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" // From node_modules
implementation "androidx.security:security-crypto:1.0.0-alpha02"
}

def configureReactNativePom(def pom) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.emeraldsanto.encryptedstorage

import android.content.Context
import android.content.SharedPreferences
import android.content.res.Resources
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
Expand All @@ -18,15 +19,21 @@ class RNEncryptedStorageModule(context : ReactApplicationContext) : ReactContext
private val sharedPreferences : SharedPreferences;

init {
this.sharedPreferences = context.getSharedPreferences(RNEncryptedStorageModule.SHARED_PREFERENCES_FILENAME, Context.MODE_PRIVATE)
this.sharedPreferences = EncryptedSharedPreferences.create(
RNEncryptedStorageModule.SHARED_PREFERENCES_FILENAME,
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);
}

override fun getName(): String {
return RNEncryptedStorageModule.NATIVE_MODULE_NAME
}

@ReactMethod
fun store(key : String, value : String, promise : Promise) {
fun setItem(key : String, value : String, promise : Promise) {
val editor = this.sharedPreferences.edit();
editor.putString(key, value);
val saved = editor.commit();
Expand All @@ -41,7 +48,7 @@ class RNEncryptedStorageModule(context : ReactApplicationContext) : ReactContext
}

@ReactMethod
fun retrieve(key : String, promise : Promise) {
fun getItem(key : String, promise : Promise) {
val value = this.sharedPreferences.getString(key, "");

if (value.isNullOrEmpty()) {
Expand All @@ -54,7 +61,7 @@ class RNEncryptedStorageModule(context : ReactApplicationContext) : ReactContext
}

@ReactMethod
fun remove(key : String, promise : Promise) {
fun removeItem(key : String, promise : Promise) {
val editor = this.sharedPreferences.edit();
editor.remove(key);
val saved = editor.commit();
Expand Down
6 changes: 3 additions & 3 deletions ios/RNEncryptedStorage.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,20 @@
@interface RCT_EXTERN_MODULE(RNEncryptedStorage, NSObject)

RCT_EXTERN_METHOD(
store: (NSString *)key
setItem: (NSString *)key
value: (NSString *)value
resolver: (RCTPromiseResolveBlock)resolve
rejecter: (RCTPromiseRejectBlock)reject
);

RCT_EXTERN_METHOD(
retrieve: (NSString *)key
getItem: (NSString *)key
resolver: (RCTPromiseResolveBlock)resolve
rejecter: (RCTPromiseRejectBlock)reject
);

RCT_EXTERN_METHOD(
remove: (NSString *)key
removeItem: (NSString *)key
resolver: (RCTPromiseResolveBlock)resolve
rejecter: (RCTPromiseRejectBlock)reject
);
Expand Down
6 changes: 3 additions & 3 deletions ios/RNEncryptedStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class RNEncryptedStorage: NSObject {
return false;
}

@objc func store(_ key : String, value : String, resolver resolve : RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) -> Void {
@objc func setItem(_ key : String, value : String, resolver resolve : RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) -> Void {
guard let dataFromValue = value.data(using: .utf8, allowLossyConversion: false) else {
return reject("Error parsing value for key \(key)", nil, nil);
}
Expand All @@ -44,7 +44,7 @@ class RNEncryptedStorage: NSObject {
}

@objc
func retrieve(_ key : String, resolver resolve : RCTPromiseResolveBlock, rejecter reject : RCTPromiseRejectBlock) {
func getItem(_ key : String, resolver resolve : RCTPromiseResolveBlock, rejecter reject : RCTPromiseRejectBlock) {
let retrieveQuery : [String : Any] = [
kSecClass as String : kSecClassGenericPassword,
kSecAttrAccount as String : key,
Expand All @@ -67,7 +67,7 @@ class RNEncryptedStorage: NSObject {
}

@objc
func remove(_ key : String, resolver resolve : RCTPromiseResolveBlock, rejecter reject : RCTPromiseRejectBlock) {
func removeItem(_ key : String, resolver resolve : RCTPromiseResolveBlock, rejecter reject : RCTPromiseRejectBlock) {
let removeQuery : [String : Any] = [
kSecClass as String : kSecClassGenericPassword,
kSecAttrAccount as String : key,
Expand Down
55 changes: 33 additions & 22 deletions lib/EncryptedStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,40 +11,51 @@ const EncryptedStorage = {
* @param {string} key - A string that will be associated to the value for later retrieval.
* @param {Object} value - The data to store.
*/
store : async function(key, value) {
try {
await RNEncryptedStorage.store(key, JSON.stringify(value));
return true;
} catch (error) {
return false;
}
setItem: async function (key, value, cb) {
return new Promise(async (resolve, reject) => {
try {
await RNEncryptedStorage.setItem(key, value);
cb && cb(null);
resolve(null);
} catch (error) {
cb && cb(error);
reject(error);
}
});
},

/**
* Retrieves data from the disk, using SharedPreferences or KeyChain, depending on the platform and returns it as the specified type.
* @param {string} key - A string that is associated to a value.
*/
retrieve : async function(key) {
try {
const savedValue = await RNEncryptedStorage.retrieve(key);
return JSON.parse(savedValue);
} catch (error) {
return undefined;
}
getItem: async function (key, cb) {
return new Promise(async (resolve, reject) => {
try {
const value = await RNEncryptedStorage.getItem(key);
cb && cb(null, value);
resolve(value);
} catch (error) {
cb && cb(error, null);
reject(error);
}
});
},

/**
* Deletes data from the disk, using SharedPreferences or KeyChain, depending on the platform.
* @param {string} key - A string that is associated to a value.
*/
remove : async function(key) {
try {
await RNEncryptedStorage.remove(key);
return true;
} catch (error) {
return false;
}
removeItem: async function (key, cb) {
return new Promise(async (resolve, reject) => {
try {
await RNEncryptedStorage.removeItem(key);
cb && cb(null);
resolve(null);
} catch (error) {
reject(error);
}
});
}
}

export default EncryptedStorage;
module.exports = EncryptedStorage;
130 changes: 130 additions & 0 deletions lib/EncryptedStorage.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
const EncryptedStorage = require('./EncryptedStorage');
const { NativeModules } = require('react-native');
const { RNEncryptedStorage } = NativeModules;

describe('lib/EncryptedStorage', () => {
afterEach(() => {
jest.clearAllMocks();
});

describe('using Promises', () => {
describe('setItem(key, value)', () => {
it('should return no errors if it could store the value', async () => {
const storeError = await EncryptedStorage.setItem('key', 'value');
expect(storeError).toBe(null);
});

it('should reject with an error if it could not store the value', async () => {
RNEncryptedStorage.setItem.mockImplementationOnce(() => Promise.reject(new Error('Set error')));

try {
await EncryptedStorage.setItem('key', 'value');
} catch (error) {
expect(error.message).toBe('Set error');
}
});
});

describe('getItem(key)', () => {
it('should return the value if it could be retrieved succesfully', async () => {
const item = await EncryptedStorage.getItem('key');
expect(item).toEqual('{ "foo": 1 }');
});

it('should return null if no value was found for that key', async () => {
RNEncryptedStorage.getItem.mockImplementationOnce(() => Promise.resolve(undefined));

const item = await EncryptedStorage.getItem('key');
expect(item).toEqual(undefined);
});

it('should reject with an error if it could not retrieve the value', async () => {
RNEncryptedStorage.getItem.mockImplementationOnce(() => Promise.reject(new Error("Get error")));

try {
await EncryptedStorage.getItem('key');
} catch (error) {
expect(error.message).toBe('Get error');
}
});
});

describe('removeItem(key)', () => {
it('should return no error if it could removed the stored value', async () => {
const removeResult = await EncryptedStorage.removeItem('key');
expect(removeResult).toBe(null);
});

it('should throw an error if it could not retrieve the stored value', async () => {
RNEncryptedStorage.removeItem.mockImplementationOnce(() => Promise.reject(new Error("Remove error")));

try {
await EncryptedStorage.removeItem('key');
} catch (error) {
expect(error.message).toBe('Remove error');
}
});
});
});

describe('using callbacks', () => {
describe('setItem(key, value)', () => {
it('should return no errors if it could store the value', () => {
EncryptedStorage.setItem('key', 'value', err => {
expect(err).toBe(null);
});
});

it('should reject with an error if it could not store the value', () => {
RNEncryptedStorage.setItem.mockImplementationOnce(() => Promise.reject(new Error('Set error')));

EncryptedStorage.setItem('key', 'value', err => {
expect(err.message).toBe('Set error');
});
});
});

describe('getItem(key)', () => {
it('should return the value if it could be retrieved succesfully', () => {
EncryptedStorage.getItem('key', (err, value) => {
expect(err).toBe(null);
expect(value).toEqual('{ "foo": 1 }');
});
});

it('should return null if no value was found for that key', () => {
RNEncryptedStorage.getItem.mockImplementationOnce(() => Promise.resolve(undefined));

EncryptedStorage.getItem('key', (err, value) => {
expect(err).toBe(null);
expect(value).toBe(undefined);
});
});

it('should reject with an error if it could not retrieve the value', () => {
RNEncryptedStorage.getItem.mockImplementationOnce(() => Promise.reject(new Error("Get error")));

EncryptedStorage.getItem('key', (err, value) => {
expect(err.message).toEqual('Get error');
expect(value).toBe(undefined);
});
});
});

describe('removeItem(key)', () => {
it('should return no error if it could removed the stored value', () => {
EncryptedStorage.removeItem('key', err => {
expect(err).toBe(null);
});
});

it('should throw an error if it could not retrieve the stored value', () => {
RNEncryptedStorage.removeItem.mockImplementationOnce(() => Promise.reject(new Error("Remove error")));

EncryptedStorage.removeItem('key', err => {
expect(err.message).toBe('Remove error');
});
});
});
});
});
Loading