Refactors native methods to use promises and deprecate callback argument

This commit is contained in:
Joel Arvidsson 2017-01-10 23:00:51 +01:00
parent 157e290a78
commit eb9093aed7
3 changed files with 108 additions and 167 deletions

View File

@ -65,13 +65,17 @@ NSString *messageForError(NSError *error)
}
}
NSDictionary * makeError(NSError *error)
NSString *codeForError(NSError *error)
{
return RCTMakeAndLogError(messageForError(error), nil, [error dictionaryWithValuesForKeys:@[@"domain", @"code"]]);
return [NSString stringWithFormat:@"%li", (long)error.code];
}
void rejectWithError(RCTPromiseRejectBlock reject, NSError *error)
{
return reject(codeForError(error), messageForError(error), nil);
}
RCT_EXPORT_METHOD(setGenericPasswordForService:(NSString*)service withUsername:(NSString*)username withPassword:(NSString*)password callback:(RCTResponseSenderBlock)callback){
RCT_EXPORT_METHOD(setGenericPasswordForService:(NSString*)service withUsername:(NSString*)username withPassword:(NSString*)password resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){
if(service == nil) {
service = [[NSBundle mainBundle] bundleIdentifier];
}
@ -91,14 +95,14 @@ RCT_EXPORT_METHOD(setGenericPasswordForService:(NSString*)service withUsername:(
if (osStatus != noErr && osStatus != errSecItemNotFound) {
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil];
return callback(@[makeError(error)]);
return rejectWithError(reject, error);
}
callback(@[[NSNull null]]);
return resolve(@(YES));
}
RCT_EXPORT_METHOD(getGenericPasswordForService:(NSString*)service callback:(RCTResponseSenderBlock)callback){
RCT_EXPORT_METHOD(getGenericPasswordForService:(NSString*)service resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){
if(service == nil) {
service = [[NSBundle mainBundle] bundleIdentifier];
}
@ -113,23 +117,27 @@ RCT_EXPORT_METHOD(getGenericPasswordForService:(NSString*)service callback:(RCTR
if (osStatus != noErr && osStatus != errSecItemNotFound) {
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil];
return callback(@[makeError(error)]);
return rejectWithError(reject, error);
}
found = (__bridge NSDictionary*)(foundTypeRef);
if (!found) {
return callback(@[[NSNull null]]);
return resolve(@(NO));
}
// Found
NSString* username = (NSString*) [found objectForKey:(__bridge id)(kSecAttrAccount)];
NSString* password = [[NSString alloc] initWithData:[found objectForKey:(__bridge id)(kSecValueData)] encoding:NSUTF8StringEncoding];
callback(@[[NSNull null], username, password]);
return resolve(@{
@"service": service,
@"username": username,
@"password": password
});
}
RCT_EXPORT_METHOD(resetGenericPasswordForService:(NSString*)service callback:(RCTResponseSenderBlock)callback){
RCT_EXPORT_METHOD(resetGenericPasswordForService:(NSString*)service resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){
if(service == nil) {
service = [[NSBundle mainBundle] bundleIdentifier];
}
@ -141,14 +149,14 @@ RCT_EXPORT_METHOD(resetGenericPasswordForService:(NSString*)service callback:(RC
OSStatus osStatus = SecItemDelete((__bridge CFDictionaryRef) dict);
if (osStatus != noErr && osStatus != errSecItemNotFound) {
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil];
return callback(@[makeError(error)]);
return rejectWithError(reject, error);
}
callback(@[[NSNull null]]);
return resolve(@(YES));
}
RCT_EXPORT_METHOD(setInternetCredentialsForServer:(NSString*)server withUsername:(NSString*)username withPassword:(NSString*)password callback:(RCTResponseSenderBlock)callback){
RCT_EXPORT_METHOD(setInternetCredentialsForServer:(NSString*)server withUsername:(NSString*)username withPassword:(NSString*)password resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){
// Create dictionary of search parameters
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassInternetPassword), kSecClass, server, kSecAttrServer, kCFBooleanTrue, kSecReturnAttributes, nil];
@ -164,14 +172,13 @@ RCT_EXPORT_METHOD(setInternetCredentialsForServer:(NSString*)server withUsername
if (osStatus != noErr && osStatus != errSecItemNotFound) {
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil];
return callback(@[makeError(error)]);
return rejectWithError(reject, error);
}
callback(@[[NSNull null]]);
return resolve(@(YES));
}
RCT_EXPORT_METHOD(getInternetCredentialsForServer:(NSString*)server callback:(RCTResponseSenderBlock)callback){
RCT_EXPORT_METHOD(getInternetCredentialsForServer:(NSString*)server resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){
// Create dictionary of search parameters
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassInternetPassword), kSecClass, server, kSecAttrServer, kCFBooleanTrue, kSecReturnAttributes, kCFBooleanTrue, kSecReturnData, nil];
@ -183,23 +190,27 @@ RCT_EXPORT_METHOD(getInternetCredentialsForServer:(NSString*)server callback:(RC
if (osStatus != noErr && osStatus != errSecItemNotFound) {
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil];
return callback(@[makeError(error)]);
return rejectWithError(reject, error);
}
found = (__bridge NSDictionary*)(foundTypeRef);
if (!found) {
return callback(@[[NSNull null]]);
return resolve(@(NO));
}
// Found
NSString* username = (NSString*) [found objectForKey:(__bridge id)(kSecAttrAccount)];
NSString* password = [[NSString alloc] initWithData:[found objectForKey:(__bridge id)(kSecValueData)] encoding:NSUTF8StringEncoding];
callback(@[[NSNull null], username, password]);
return resolve(@{
@"server": server,
@"username": username,
@"password": password
});
}
RCT_EXPORT_METHOD(resetInternetCredentialsForServer:(NSString*)server callback:(RCTResponseSenderBlock)callback){
RCT_EXPORT_METHOD(resetInternetCredentialsForServer:(NSString*)server resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){
// Create dictionary of search parameters
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassInternetPassword), kSecClass, server, kSecAttrServer, kCFBooleanTrue, kSecReturnAttributes, kCFBooleanTrue, kSecReturnData, nil];
@ -208,10 +219,10 @@ RCT_EXPORT_METHOD(resetInternetCredentialsForServer:(NSString*)server callback:(
OSStatus osStatus = SecItemDelete((__bridge CFDictionaryRef) dict);
if (osStatus != noErr && osStatus != errSecItemNotFound) {
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil];
return callback(@[makeError(error)]);
return rejectWithError(reject, error);
}
callback(@[[NSNull null]]);
return resolve(@(YES));
}
@ -222,14 +233,14 @@ RCT_EXPORT_METHOD(requestSharedWebCredentials:(RCTPromiseResolveBlock)resolve re
NSError *nsError = (__bridge NSError *)error;
return reject([NSString stringWithFormat:@"%li", (long)nsError.code], nsError.description, nil);
}
if (CFArrayGetCount(credentials) > 0) {
CFDictionaryRef credentialDict = CFArrayGetValueAtIndex(credentials, 0);
NSString *server = (__bridge NSString *)CFDictionaryGetValue(credentialDict, kSecAttrServer);
NSString *username = (__bridge NSString *)CFDictionaryGetValue(credentialDict, kSecAttrAccount);
NSString *password = (__bridge NSString *)CFDictionaryGetValue(credentialDict, kSecSharedPassword);
return resolve(@{
@"server": server,
@"username": username,

View File

@ -11,10 +11,12 @@ import com.facebook.crypto.Crypto;
import com.facebook.crypto.CryptoConfig;
import com.facebook.crypto.Entity;
import com.facebook.crypto.keychain.KeyChain;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;
import java.nio.charset.Charset;
@ -41,15 +43,15 @@ public class KeychainModule extends ReactContextBaseJavaModule {
}
@ReactMethod
public void setGenericPasswordForService(String service, String username, String password, Callback callback) {
public void setGenericPasswordForService(String service, String username, String password, Promise promise) {
if (!crypto.isAvailable()) {
Log.e(KEYCHAIN_MODULE, "Crypto is missing");
callback.invoke("KeychainModule: crypto is missing");
promise.reject("KeychainModule: crypto is missing");
return;
}
if (username == null || username.isEmpty() || password == null || password.isEmpty()) {
Log.e(KEYCHAIN_MODULE, "you passed empty or null username/password");
callback.invoke("KeychainModule: you passed empty or null username/password");
promise.reject("KeychainModule: you passed empty or null username/password");
return;
}
service = service == null ? EMPTY_STRING : service;
@ -59,37 +61,37 @@ public class KeychainModule extends ReactContextBaseJavaModule {
Entity pwentity = Entity.create(KEYCHAIN_DATA + ":" + service + "pass");
String encryptedUsername = encryptWithEntity(username, userentity, callback);
String encryptedPassword = encryptWithEntity(password, pwentity, callback);
String encryptedUsername = encryptWithEntity(username, userentity, promise);
String encryptedPassword = encryptWithEntity(password, pwentity, promise);
SharedPreferences.Editor prefsEditor = prefs.edit();
prefsEditor.putString(service + ":u", encryptedUsername);
prefsEditor.putString(service + ":p", encryptedPassword);
prefsEditor.apply();
Log.d(KEYCHAIN_MODULE, "saved the data");
callback.invoke(EMPTY_STRING, "KeychainModule saved the data");
promise.resolve("KeychainModule saved the data");
}
private String encryptWithEntity(String toEncypt, Entity entity, Callback callback) {
private String encryptWithEntity(String toEncypt, Entity entity, Promise promise) {
try {
byte[] encryptedBytes = crypto.encrypt(toEncypt.getBytes(Charset.forName("UTF-8")), entity);
return Base64.encodeToString(encryptedBytes, Base64.DEFAULT);
} catch (Exception e) {
Log.e(KEYCHAIN_MODULE, e.getLocalizedMessage());
callback.invoke(e.getLocalizedMessage());
promise.reject(e.getLocalizedMessage(), e);
return null;
}
}
@ReactMethod
public void getGenericPasswordForService(String service, Callback callback) {
public void getGenericPasswordForService(String service, Promise promise) {
service = service == null ? EMPTY_STRING : service;
String username = prefs.getString(service + ":u", "user_not_found");
String password = prefs.getString(service + ":p", "pass_not_found");
if (username.equals("user_not_found") || password.equals("pass_not_found")) {
Log.e(KEYCHAIN_MODULE, "no keychain entry found for service: " + service);
callback.invoke("no keychain entry found for service: " + service);
promise.reject("no keychain entry found for service: " + service);
return;
}
@ -103,15 +105,21 @@ public class KeychainModule extends ReactContextBaseJavaModule {
byte[] decryptedUsername = crypto.decrypt(recuser, userentity);
byte[] decryptedPass = crypto.decrypt(recpass, pwentity);
callback.invoke(EMPTY_STRING, new String(decryptedUsername, Charset.forName("UTF-8")), new String(decryptedPass, Charset.forName("UTF-8")));
WritableMap credentials = Arguments.createMap();
credentials.putString("service", service);
credentials.putString("username", new String(decryptedUsername, Charset.forName("UTF-8")));
credentials.putString("password", new String(decryptedPass, Charset.forName("UTF-8")));
promise.resolve(credentials);
} catch (Exception e) {
Log.e(KEYCHAIN_MODULE, e.getLocalizedMessage());
callback.invoke(e.getLocalizedMessage());
promise.reject(e.getLocalizedMessage(), e);
}
}
@ReactMethod
public void resetGenericPasswordForService(String service, Callback callback) {
public void resetGenericPasswordForService(String service, Promise promise) {
service = service == null ? EMPTY_STRING : service;
SharedPreferences.Editor prefsEditor = prefs.edit();
@ -119,25 +127,25 @@ public class KeychainModule extends ReactContextBaseJavaModule {
prefsEditor.remove(service + ":u");
prefsEditor.remove(service + ":p");
prefsEditor.apply();
callback.invoke(EMPTY_STRING, "KeychainModule password was reset");
promise.resolve("KeychainModule password was reset");
} else {
callback.invoke("Error when resetting password: entry not found for service: " + service);
promise.reject("Error when resetting password: entry not found for service: " + service);
}
}
@ReactMethod
public void setInternetCredentialsForServer(@NonNull String server, String username, String password, Callback callback) {
setGenericPasswordForService(server, username, password, callback);
public void setInternetCredentialsForServer(@NonNull String server, String username, String password, Promise promise) {
setGenericPasswordForService(server, username, password, promise);
}
@ReactMethod
public void getInternetCredentialsForServer(@NonNull String server, Callback callback) {
getGenericPasswordForService(server, callback);
public void getInternetCredentialsForServer(@NonNull String server, Promise promise) {
getGenericPasswordForService(server, promise);
}
@ReactMethod
public void resetInternetCredentialsForServer(@NonNull String server, Callback callback) {
resetGenericPasswordForService(server, callback);
public void resetInternetCredentialsForServer(@NonNull String server, Promise promise) {
resetGenericPasswordForService(server, promise);
}

160
index.js
View File

@ -1,165 +1,84 @@
import { NativeModules, Platform } from 'react-native';
const { RNKeychainManager } = NativeModules;
function convertError(err) {
if (!err) {
return null;
}
if (Platform.OS === 'android') {
return new Error(err);
}
var out = new Error(err.message);
out.key = err.key;
return out;
}
/**
* Saves the `username` and `password` combination for `server`
* and calls `callback` with an `Error` if there is any.
* Returns a `Promise` object.
* Saves the `username` and `password` combination for `server`.
* @param {string} server URL to server.
* @param {string} username Associated username or e-mail to be saved.
* @param {string} password Associated password to be saved.
* @return {Promise} Resolves to `true` when successful
*/
export function setInternetCredentials(
server: string,
username: string,
password: string,
callback?: ?(error: ?Error) => void
password: string
): Promise {
return new Promise((resolve, reject) => {
RNKeychainManager.setInternetCredentialsForServer(server, username, password, function(err) {
err = convertError(err);
callback && callback(err || null);
if (err) {
reject(err);
} else {
resolve();
}
});
});
return RNKeychainManager.setInternetCredentialsForServer(server, username, password);
}
/**
* Fetches login combination for `server` as an object with the format `{ username, password }`
* and passes the result to `callback`, along with an `Error` if there is any.
* Returns a `Promise` object.
* Fetches login combination for `server`.
* @param {string} server URL to server.
* @return {Promise} Resolves to `{ server, username, password }` when successful
*/
export function getInternetCredentials(
server: string,
callback?: ?(error: ?Error, username: ?string, password: ?string) => void
server: string
): Promise {
return new Promise((resolve, reject) => {
RNKeychainManager.getInternetCredentialsForServer(server, function(err, username, password) {
err = convertError(err);
if(!err && arguments.length === 1) {
err = new Error('No keychain entry found for server "' + server + '"');
}
callback && callback(err || null, username, password);
if (err) {
reject(err);
} else {
resolve({ username, password });
}
});
});
return RNKeychainManager.getInternetCredentialsForServer(server);
}
/**
* Deletes all internet password keychain entries for `server` and calls `callback` with an
* `Error` if there is any.
* Returns a `Promise` object.
* Deletes all internet password keychain entries for `server`.
* @param {string} server URL to server.
* @return {Promise} Resolves to `true` when successful
*/
export function resetInternetCredentials(
server: string,
callback?: ?(error: ?Error) => void
server: string
): Promise {
return new Promise((resolve, reject) => {
RNKeychainManager.resetInternetCredentialsForServer(server, function(err) {
err = convertError(err);
callback && callback(err || null);
if (err) {
reject(err);
} else {
resolve();
}
});
});
return RNKeychainManager.resetInternetCredentialsForServer(server);
}
/**
* Saves the `username` and `password` combination for `service` (defaults to `bundleId`)
* and calls `callback` with an `Error` if there is any.
* Returns a `Promise` object.
* Saves the `username` and `password` combination for `service`.
* @param {string} username Associated username or e-mail to be saved.
* @param {string} password Associated password to be saved.
* @param {string} service Reverse domain name qualifier for the service, defaults to `bundleId`.
* @return {Promise} Resolves to `true` when successful
*/
export function setGenericPassword(
username: string,
password: string,
service?: string,
callback?: ?(error: ?Error) => void
service?: string
): Promise {
return new Promise((resolve, reject) => {
RNKeychainManager.setGenericPasswordForService(service, username, password, function(err) {
err = convertError(err);
callback && callback(err || null);
if (err) {
reject(err);
} else {
resolve();
}
});
});
return RNKeychainManager.setGenericPasswordForService(service, username, password);
}
/**
* Fetches login combination for `service` (defaults to `bundleId`) as an object with the format
* `{ username, password }` and passes the result to `callback`, along with an `Error` if
* there is any.
* Returns a `Promise` object.
* Fetches login combination for `service`.
* @param {string} service Reverse domain name qualifier for the service, defaults to `bundleId`.
* @return {Promise} Resolves to `{ service, username, password }` when successful
*/
export function getGenericPassword(
service?: string,
callback?: ?(error: ?Error, username: ?string, password: ?string) => void
service?: string
): Promise {
return new Promise((resolve, reject) => {
RNKeychainManager.getGenericPasswordForService(service, function(err, username, password) {
err = convertError(err);
if(!err && arguments.length === 1) {
err = new Error('No keychain entry found' + (service ? ' for service "' + service + '"' : ''));
}
callback && callback(err || null, username, password);
if (err) {
reject(err);
} else {
resolve({ username, password });
}
});
});
return RNKeychainManager.getGenericPasswordForService(service);
}
/**
* Deletes all generic password keychain entries for `service` (defaults to `bundleId`) and calls
* `callback` with an `Error` if there is any.
* Returns a `Promise` object.
* Deletes all generic password keychain entries for `service`.
* @param {string} service Reverse domain name qualifier for the service, defaults to `bundleId`.
* @return {Promise} Resolves to `true` when successful
*/
export function resetGenericPassword(
service?: string,
callback?: ?(error: ?Error) => void
service?: string
): Promise {
return new Promise((resolve, reject) => {
RNKeychainManager.resetGenericPasswordForService(service, function(err) {
err = convertError(err);
callback && callback(err || null);
if (err) {
reject(err);
} else {
resolve();
}
});
});
return RNKeychainManager.resetGenericPasswordForService(service);
}
/**
* Asks the user for a shared web credential, resolves to `{ server, username, password }` if approved
* `false` if denied and throws an error if not supported on platform or there's no shared credentials.
* Returns a `Promise` object.
* Asks the user for a shared web credential.
* @return {Promise} Resolves to `{ server, username, password }` if approved and
* `false` if denied and throws an error if not supported on platform or there's no shared credentials
*/
export function requestSharedWebCredentials() : Promise {
if (Platform.OS !== 'ios') {
@ -170,7 +89,10 @@ export function requestSharedWebCredentials() : Promise {
/**
* Sets a shared web credential.
* Returns a `Promise` object.
* @param {string} server URL to server.
* @param {string} username Associated username or e-mail to be saved.
* @param {string} password Associated password to be saved.
* @return {Promise} Resolves to `true` when successful
*/
export function setSharedWebCredentials(
server: string,