├── android ├── .settings │ └── org.eclipse.buildship.core.prefs ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── localz │ │ ├── pinch │ │ ├── models │ │ │ ├── HttpResponse.java │ │ │ └── HttpRequest.java │ │ └── utils │ │ │ ├── JsonUtil.java │ │ │ ├── KeyPinStoreUtil.java │ │ │ └── HttpUtil.java │ │ ├── PinchPackage.java │ │ └── RNPinch.java ├── .project ├── build.gradle ├── gradlew.bat └── gradlew ├── .gitignore ├── RNPinch.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcuserdata │ │ └── samitha.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcuserdata │ └── samitha.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── project.pbxproj ├── RNPinch ├── RNPinch.h └── RNPinch.m ├── react-native-pinch-new.podspec ├── package.json ├── index.js └── README.md /android/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | connection.project.dir= 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | android/build/ 2 | android/release/ 3 | android/.idea 4 | android/.gradle 5 | android/local.properties 6 | *.iml 7 | .DS_Store -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samitha9125/react-native-pinch-new/HEAD/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /RNPinch.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /RNPinch.xcodeproj/project.xcworkspace/xcuserdata/samitha.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samitha9125/react-native-pinch-new/HEAD/RNPinch.xcodeproj/project.xcworkspace/xcuserdata/samitha.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Sep 27 11:09:28 IST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip 7 | -------------------------------------------------------------------------------- /RNPinch.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /RNPinch.xcodeproj/xcuserdata/samitha.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | RNPinch.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /RNPinch/RNPinch.h: -------------------------------------------------------------------------------- 1 | // 2 | // RNNativeFetch.h 3 | // medipass 4 | // 5 | // Created by Paul Wong on 13/10/16. 6 | // Copyright © 2016 Localz. All rights reserved. 7 | // 8 | 9 | #import 10 | #if __has_include() 11 | #import "React/RCTBridgeModule.h" 12 | #import "React/RCTLog.h" 13 | #else 14 | #import "RCTBridgeModule.h" 15 | #import "RCTLog.h" 16 | #endif 17 | 18 | @interface RNPinch : NSObject 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /android/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | android 4 | Project android created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.buildship.core.gradleprojectbuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.buildship.core.gradleprojectnature 16 | 17 | 18 | -------------------------------------------------------------------------------- /react-native-pinch-new.podspec: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, './package.json'))) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = package['name'] 7 | s.version = package['version'] 8 | s.summary = package['description'] 9 | s.license = package['license'] 10 | 11 | s.authors = package['author'] 12 | s.homepage = package['homepage'] 13 | s.platform = :ios, "9.0" 14 | 15 | s.source = { :git => "https://github.com/samitha9125/react-native-pinch-new.git", :tag => "master" } 16 | s.source_files = "RNPinch/*.{h,m}" 17 | 18 | s.dependency 'React' 19 | end -------------------------------------------------------------------------------- /android/src/main/java/com/localz/pinch/models/HttpResponse.java: -------------------------------------------------------------------------------- 1 | package com.localz.pinch.models; 2 | 3 | import com.facebook.react.bridge.WritableMap; 4 | 5 | public class HttpResponse { 6 | public int statusCode; 7 | public WritableMap headers; 8 | public String bodyString; 9 | public String statusText; 10 | 11 | public HttpResponse() {} 12 | 13 | public HttpResponse(int statusCode, WritableMap headers, String bodyString, String statusText) { 14 | this.statusCode = statusCode; 15 | this.headers = headers; 16 | this.bodyString = bodyString; 17 | this.statusText = statusText; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.library" 2 | 3 | //import com.android.build.OutputFile 4 | 5 | def safeExtGet(prop, fallback) { 6 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 7 | } 8 | 9 | android { 10 | compileSdkVersion safeExtGet('compileSdkVersion', 28) 11 | buildToolsVersion safeExtGet('buildToolsVersion', '28.0.3') 12 | 13 | defaultConfig { 14 | minSdkVersion 16 15 | targetSdkVersion safeExtGet('targetSdkVersion', 28) 16 | versionCode 1 17 | versionName "1.0" 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation "com.facebook.react:react-native:+" // From node_modules 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-pinch-new", 3 | "version": "0.1.15", 4 | "description": "React Native fetch with SSL Pinning support", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/samitha9125/react-native-pinch-new" 12 | }, 13 | "keywords": [ 14 | "react-native", 15 | "ios", 16 | "android", 17 | "SSL", 18 | "SSL-Pinning", 19 | "Network" 20 | ], 21 | "author": "Samitha9125", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/samitha9125/react-native-pinch-new/issues" 25 | }, 26 | "homepage": "https://github.com/samitha9125/react-native-pinch-new#readme", 27 | "devDependencies": { 28 | "react": "15.3.2", 29 | "react-native": "^0.34" 30 | }, 31 | "peerDependencies": { 32 | "react-native": ">=0.34 <1.0.0" 33 | }, 34 | "dependencies": { 35 | "q": "^1.4.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /android/src/main/java/com/localz/pinch/models/HttpRequest.java: -------------------------------------------------------------------------------- 1 | package com.localz.pinch.models; 2 | 3 | import org.json.JSONObject; 4 | 5 | public class HttpRequest { 6 | public String endpoint; 7 | public String method; 8 | public JSONObject headers; 9 | public String body; 10 | public boolean ignore_ssl; 11 | public String[] certFilenames; 12 | public int timeout; 13 | 14 | private static final int DEFAULT_TIMEOUT = 10000; 15 | 16 | public HttpRequest() { 17 | this.timeout = DEFAULT_TIMEOUT; 18 | } 19 | 20 | public HttpRequest(String endpoint) { 21 | this.endpoint = endpoint; 22 | this.timeout = DEFAULT_TIMEOUT; 23 | } 24 | 25 | public HttpRequest(String endpoint, String method, JSONObject headers, String body, String[] certFilenames, int timeout) { 26 | this.endpoint = endpoint; 27 | this.method = method; 28 | this.headers = headers; 29 | this.body = body; 30 | this.certFilenames = certFilenames; 31 | this.timeout = timeout; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /android/src/main/java/com/localz/PinchPackage.java: -------------------------------------------------------------------------------- 1 | package com.localz; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.JavaScriptModule; 5 | import com.facebook.react.bridge.NativeModule; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.uimanager.ViewManager; 8 | import com.localz.RNPinch; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | public class PinchPackage implements ReactPackage { 15 | 16 | public List> createJSModules() { 17 | return Collections.emptyList(); 18 | } 19 | 20 | @Override 21 | public List createViewManagers(ReactApplicationContext reactContext) { 22 | return Collections.emptyList(); 23 | } 24 | 25 | @Override 26 | public List createNativeModules( 27 | ReactApplicationContext reactContext) { 28 | List modules = new ArrayList<>(); 29 | modules.add(new RNPinch(reactContext)); 30 | return modules; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /android/src/main/java/com/localz/pinch/utils/JsonUtil.java: -------------------------------------------------------------------------------- 1 | package com.localz.pinch.utils; 2 | 3 | import com.facebook.react.bridge.ReadableArray; 4 | import com.facebook.react.bridge.ReadableMap; 5 | import com.facebook.react.bridge.ReadableMapKeySetIterator; 6 | 7 | import org.json.JSONArray; 8 | import org.json.JSONException; 9 | import org.json.JSONObject; 10 | 11 | public class JsonUtil { 12 | public static JSONArray convertArrayToJson(ReadableArray readableArray) throws JSONException { 13 | JSONArray array = new JSONArray(); 14 | for (int i = 0; i < readableArray.size(); i++) { 15 | switch (readableArray.getType(i)) { 16 | case Null: 17 | break; 18 | case Boolean: 19 | array.put(readableArray.getBoolean(i)); 20 | break; 21 | case Number: 22 | array.put(readableArray.getDouble(i)); 23 | break; 24 | case String: 25 | array.put(readableArray.getString(i)); 26 | break; 27 | case Map: 28 | array.put(convertReadableMapToJson(readableArray.getMap(i))); 29 | break; 30 | case Array: 31 | array.put(convertArrayToJson(readableArray.getArray(i))); 32 | break; 33 | } 34 | } 35 | return array; 36 | } 37 | 38 | public static JSONObject convertReadableMapToJson(ReadableMap readableMap) throws JSONException { 39 | JSONObject object = new JSONObject(); 40 | ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); 41 | while (iterator.hasNextKey()) { 42 | String key = iterator.nextKey(); 43 | switch (readableMap.getType(key)) { 44 | case Null: 45 | object.put(key, JSONObject.NULL); 46 | break; 47 | case Boolean: 48 | object.put(key, readableMap.getBoolean(key)); 49 | break; 50 | case Number: 51 | object.put(key, readableMap.getDouble(key)); 52 | break; 53 | case String: 54 | object.put(key, readableMap.getString(key)); 55 | break; 56 | case Map: 57 | object.put(key, convertReadableMapToJson(readableMap.getMap(key))); 58 | break; 59 | case Array: 60 | object.put(key, convertArrayToJson(readableMap.getArray(key))); 61 | break; 62 | } 63 | } 64 | return object; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /android/src/main/java/com/localz/pinch/utils/KeyPinStoreUtil.java: -------------------------------------------------------------------------------- 1 | package com.localz.pinch.utils; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.security.KeyManagementException; 7 | import java.security.KeyStore; 8 | import java.security.KeyStoreException; 9 | import java.security.NoSuchAlgorithmException; 10 | import java.security.cert.Certificate; 11 | import java.security.cert.CertificateException; 12 | import java.security.cert.CertificateFactory; 13 | import java.security.cert.X509Certificate; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | import javax.net.ssl.SSLContext; 18 | import javax.net.ssl.TrustManagerFactory; 19 | 20 | public class KeyPinStoreUtil { 21 | 22 | private static HashMap instances = new HashMap<>(); 23 | private SSLContext sslContext = SSLContext.getInstance("TLS"); 24 | 25 | public static synchronized KeyPinStoreUtil getInstance(String[] filenames) throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException { 26 | if (filenames != null && instances.get(filenames) == null) { 27 | instances.put(filenames, new KeyPinStoreUtil(filenames)); 28 | } 29 | return instances.get(filenames); 30 | 31 | } 32 | 33 | private KeyPinStoreUtil(String[] filenames) throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException { 34 | CertificateFactory cf = CertificateFactory.getInstance("X.509"); 35 | 36 | // Create a KeyStore for our trusted CAs 37 | String keyStoreType = KeyStore.getDefaultType(); 38 | KeyStore keyStore = KeyStore.getInstance(keyStoreType); 39 | keyStore.load(null, null); 40 | 41 | for (String filename : filenames) { 42 | InputStream caInput = new BufferedInputStream(this.getClass().getClassLoader().getResourceAsStream("assets/" + filename + ".cer")); 43 | Certificate ca; 44 | try { 45 | ca = cf.generateCertificate(caInput); 46 | System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN()); 47 | } finally { 48 | caInput.close(); 49 | } 50 | 51 | keyStore.setCertificateEntry(filename, ca); 52 | } 53 | 54 | // Create a TrustManager that trusts the CAs in our KeyStore 55 | String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); 56 | TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm); 57 | tmf.init(keyStore); 58 | 59 | sslContext.init(null, tmf.getTrustManagers(), null); 60 | } 61 | 62 | public SSLContext getContext() { 63 | return sslContext; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { NativeModules, Platform } from 'react-native'; 4 | var Q = require('q'); 5 | 6 | var RNPinch = { 7 | fetch: function (url, obj, callback) { 8 | var deferred = Q.defer(); 9 | NativeModules.RNPinch.fetch(url, obj, (err, res) => { 10 | //var err = {...err}; 11 | if (err) { 12 | switch (getKeyByValue(err.code)) { 13 | case 'kCFURLErrorTimedOut': 14 | err.reason = 'Timeout'; 15 | break; 16 | case 'kCFURLErrorUnsupportedURL': 17 | case 'kCFURLErrorCannotFindHost': 18 | case 'kCFURLErrorCannotConnectToHost': 19 | case 'kCFURLErrorNetworkConnectionLost': 20 | case 'kCFURLErrorDNSLookupFailed': 21 | case 'kCFURLErrorHTTPTooManyRedirects': 22 | case 'kCFURLErrorResourceUnavailable': 23 | case 'kCFURLErrorNotConnectedToInternet': 24 | err.reason = 'Network error'; 25 | break; 26 | default: 27 | err.reason = 'Unknown error'; 28 | break; 29 | } 30 | deferred.reject(err); 31 | } else { 32 | res.json = function () { 33 | return Q.fcall(function () { 34 | return JSON.parse(res.bodyString); 35 | }); 36 | }; 37 | res.text = function () { 38 | return Q.fcall(function () { 39 | return res.bodyString; 40 | }); 41 | }; 42 | res.url = url; 43 | deferred.resolve(res); 44 | } 45 | 46 | deferred.promise.nodeify(callback); 47 | }); 48 | return deferred.promise; 49 | } 50 | }; 51 | 52 | const obj = { 53 | kCFURLErrorUnknown: -998, 54 | kCFURLErrorCancelled: -999, 55 | kCFURLErrorBadURL: -1000, 56 | kCFURLErrorTimedOut: -1001, 57 | kCFURLErrorUnsupportedURL: -1002, 58 | kCFURLErrorCannotFindHost: -1003, 59 | kCFURLErrorCannotConnectToHost: -1004, 60 | kCFURLErrorNetworkConnectionLost: -1005, 61 | kCFURLErrorDNSLookupFailed: -1006, 62 | kCFURLErrorHTTPTooManyRedirects: -1007, 63 | kCFURLErrorResourceUnavailable: -1008, 64 | kCFURLErrorNotConnectedToInternet: -1009, 65 | kCFURLErrorRedirectToNonExistentLocation: -1010, 66 | kCFURLErrorBadServerResponse: -1011, 67 | kCFURLErrorUserCancelledAuthentication: -1012, 68 | kCFURLErrorUserAuthenticationRequired: -1013, 69 | kCFURLErrorZeroByteResource: -1014, 70 | kCFURLErrorCannotDecodeRawData: -1015, 71 | kCFURLErrorCannotDecodeContentData: -1016, 72 | kCFURLErrorCannotParseResponse: -1017, 73 | kCFURLErrorInternationalRoamingOff: -1018, 74 | kCFURLErrorCallIsActive: -1019, 75 | kCFURLErrorDataNotAllowed: -1020, 76 | kCFURLErrorRequestBodyStreamExhausted: -1021, 77 | kCFURLErrorFileDoesNotExist: -1100, 78 | kCFURLErrorFileIsDirectory: -1101, 79 | kCFURLErrorNoPermissionsToReadFile: -1102, 80 | kCFURLErrorDataLengthExceedsMaximum: -1103, 81 | } 82 | 83 | function getKeyByValue(value) { 84 | return Object.keys(obj).find(key => obj[key] === value); 85 | } 86 | 87 | module.exports = RNPinch; 88 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /android/src/main/java/com/localz/pinch/utils/HttpUtil.java: -------------------------------------------------------------------------------- 1 | package com.localz.pinch.utils; 2 | 3 | import android.util.Log; 4 | 5 | import com.facebook.react.bridge.Arguments; 6 | import com.facebook.react.bridge.ReadableMapKeySetIterator; 7 | import com.facebook.react.bridge.WritableMap; 8 | 9 | import java.net.HttpURLConnection; 10 | import java.net.URI; 11 | import java.net.URISyntaxException; 12 | 13 | import com.localz.pinch.models.HttpRequest; 14 | import com.localz.pinch.models.HttpResponse; 15 | 16 | import org.json.JSONException; 17 | import org.json.JSONObject; 18 | 19 | import java.io.BufferedReader; 20 | import java.io.IOException; 21 | import java.io.InputStreamReader; 22 | import java.io.InputStream; 23 | import java.io.OutputStream; 24 | import java.net.URL; 25 | import java.security.KeyManagementException; 26 | import java.security.KeyStoreException; 27 | import java.security.NoSuchAlgorithmException; 28 | import java.security.cert.CertificateException; 29 | import java.util.Iterator; 30 | import java.util.List; 31 | import java.util.Map; 32 | 33 | import javax.net.ssl.HttpsURLConnection; 34 | 35 | public class HttpUtil { 36 | private static final String DEFAULT_CONTENT_TYPE = "application/json"; 37 | 38 | private String getResponseBody(InputStream responseStream) throws IOException { 39 | BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(responseStream)); 40 | StringBuilder sb = new StringBuilder(); 41 | String line; 42 | 43 | while ((line = bufferedReader.readLine()) != null) { 44 | sb.append(line); 45 | } 46 | bufferedReader.close(); 47 | 48 | return sb.toString(); 49 | } 50 | 51 | private WritableMap getResponseHeaders(HttpURLConnection connection) { 52 | WritableMap jsonHeaders = Arguments.createMap(); 53 | Map> headerMap = connection.getHeaderFields(); 54 | 55 | for (Map.Entry> entry : headerMap.entrySet()) { 56 | if (entry.getKey() != null) { 57 | jsonHeaders.putString(entry.getKey(), entry.getValue().get(0)); 58 | } 59 | } 60 | 61 | return jsonHeaders; 62 | } 63 | 64 | private HttpURLConnection prepareRequestHeaders(HttpURLConnection connection, JSONObject headers) throws JSONException { 65 | connection.setRequestProperty("Content-Type", DEFAULT_CONTENT_TYPE); 66 | connection.setRequestProperty("Accept", DEFAULT_CONTENT_TYPE); 67 | 68 | if (headers != null) { 69 | Iterator iterator = headers.keys(); 70 | while (iterator.hasNext()) { 71 | String nextKey = iterator.next(); 72 | connection.setRequestProperty(nextKey, headers.get(nextKey).toString()); 73 | } 74 | } 75 | 76 | return connection; 77 | } 78 | 79 | private HttpURLConnection prepareRequest(HttpRequest request) 80 | throws IOException, KeyStoreException, CertificateException, KeyManagementException, NoSuchAlgorithmException, JSONException { 81 | HttpURLConnection connection; 82 | URL url = new URL(request.endpoint); 83 | String method = request.method.toUpperCase(); 84 | 85 | if(request.endpoint.startsWith("https") && !request.ignore_ssl) { 86 | HttpsURLConnection httpsConnection = (HttpsURLConnection) url.openConnection(); 87 | if (request.certFilenames != null && request.certFilenames.length > 0) { 88 | httpsConnection.setSSLSocketFactory(KeyPinStoreUtil.getInstance(request.certFilenames).getContext().getSocketFactory()); 89 | } 90 | connection = httpsConnection; 91 | } else if(request.endpoint.startsWith("https") && request.ignore_ssl) { 92 | HttpsURLConnection httpsConnection = (HttpsURLConnection) url.openConnection(); 93 | connection = httpsConnection; 94 | }else{ 95 | connection = (HttpURLConnection) url.openConnection(); 96 | } 97 | connection.setRequestMethod(method); 98 | 99 | connection = prepareRequestHeaders(connection, request.headers); 100 | 101 | connection.setRequestProperty("Accept-Charset", "UTF-8"); 102 | connection.setAllowUserInteraction(false); 103 | connection.setConnectTimeout(request.timeout); 104 | connection.setReadTimeout(request.timeout); 105 | 106 | if (request.body != null && (method.equals("POST") || method.equals("PUT") || method.equals("PATCH"))) { 107 | // Set the content length of the body. 108 | connection.setRequestProperty("Content-length", request.body.getBytes().length + ""); 109 | connection.setDoInput(true); 110 | connection.setDoOutput(true); 111 | connection.setUseCaches(false); 112 | 113 | // Send the JSON as body of the request. 114 | OutputStream outputStream = connection.getOutputStream(); 115 | outputStream.write(request.body.getBytes("UTF-8")); 116 | outputStream.close(); 117 | } 118 | 119 | return connection; 120 | } 121 | 122 | private InputStream prepareResponseStream(HttpURLConnection connection) throws IOException { 123 | try { 124 | return connection.getInputStream(); 125 | } catch (IOException e) { 126 | return connection.getErrorStream(); 127 | } 128 | } 129 | 130 | public HttpResponse sendHttpRequest(HttpRequest request) 131 | throws IOException, KeyStoreException, CertificateException, KeyManagementException, NoSuchAlgorithmException, JSONException { 132 | InputStream responseStream = null; 133 | HttpResponse response = new HttpResponse(); 134 | HttpURLConnection connection; 135 | int status; 136 | String statusText; 137 | 138 | try { 139 | connection = prepareRequest(request); 140 | 141 | connection.connect(); 142 | 143 | status = connection.getResponseCode(); 144 | statusText = connection.getResponseMessage(); 145 | responseStream = prepareResponseStream(connection); 146 | 147 | response.statusCode = status; 148 | response.statusText = statusText; 149 | response.bodyString = getResponseBody(responseStream); 150 | response.headers = getResponseHeaders(connection); 151 | 152 | return response; 153 | } finally { 154 | if (responseStream != null) { 155 | responseStream.close(); 156 | } 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This library is created because currently react-native-pinch library is not maintaining actively. Also this library supports HTTP and HTTPS. 2 | 3 | # React Native Pinch New 👌 4 | 5 | Callback and promise based HTTP client that supports SSL pinning for React Native. 6 | 7 | ## Installation 8 | 9 | Using NPM: 10 | ``` 11 | npm install react-native-pinch-new 12 | ``` 13 | 14 | Using Yarn: 15 | ``` 16 | yarn add react-native-pinch-new 17 | ``` 18 | ## Supports the new Autolinking 19 | 20 | ## Automatically link (Only recommended when autolinking failed) 21 | 22 | #### With React Native 0.27+ 23 | 24 | ```shell 25 | react-native link react-native-pinch-new 26 | ``` 27 | 28 | #### With older versions of React Native 29 | 30 | You need [`rnpm`](https://github.com/rnpm/rnpm) (`npm install -g rnpm`) 31 | 32 | ```shell 33 | rnpm link react-native-pinch-new 34 | ``` 35 | 36 | ## Manually link 37 | 38 | ### iOS (via Cocoa Pods) 39 | Add the following line to your build targets in your `Podfile` 40 | 41 | `pod 'react-native-pinch-new', :path => '../node_modules/react-native-pinch-new'` 42 | 43 | Then run `pod install` 44 | 45 | ### Android 46 | 47 | - in `android/app/build.gradle`: 48 | 49 | ```diff 50 | dependencies { 51 | ... 52 | compile "com.facebook.react:react-native:+" // From node_modules 53 | + compile project(':react-native-pinch') 54 | } 55 | ``` 56 | 57 | - in `android/settings.gradle`: 58 | 59 | ```diff 60 | ... 61 | include ':app' 62 | + include ':react-native-pinch' 63 | + project(':react-native-pinch').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-pinch-new/android') 64 | ``` 65 | 66 | #### With React Native 0.29+ 67 | 68 | - in `MainApplication.java`: 69 | 70 | ```diff 71 | + import com.localz.PinchPackage; 72 | 73 | public class MainApplication extends Application implements ReactApplication { 74 | //...... 75 | 76 | @Override 77 | protected List getPackages() { 78 | return Arrays.asList( 79 | + new PinchPackage(), 80 | new MainReactPackage() 81 | ); 82 | } 83 | 84 | ...... 85 | } 86 | ``` 87 | 88 | #### With older versions of React Native: 89 | 90 | - in `MainActivity.java`: 91 | 92 | ```diff 93 | + import com.localz.PinchPackage; 94 | 95 | public class MainActivity extends ReactActivity { 96 | ...... 97 | 98 | @Override 99 | protected List getPackages() { 100 | return Arrays.asList( 101 | + new PinchPackage(), 102 | new MainReactPackage() 103 | ); 104 | } 105 | } 106 | ``` 107 | 108 | ## Adding certificates 109 | 110 | Before you can make requests using SSL pinning, you first need to add your `.cer` files to your project's assets. 111 | 112 | ### Android 113 | 114 | - Place your `.cer` files under `src/main/assets/`. 115 | 116 | ### iOS 117 | 118 | - Place your `.der` files in your iOS Project. Don't forget to add them in your `Build Phases > Copy Bundle Resources`, in Xcode. 119 | 120 | 121 | ## Example 122 | *Examples are using the ES6 standard* 123 | 124 | Requests can be made by using the `fetch(url[, config, [callback]])` method of Pinch. 125 | 126 | ### Using Promises 127 | ```javascript 128 | import pinch from 'react-native-pinch-new'; 129 | 130 | pinch.fetch('https://my-api.com/v1/endpoint', { 131 | method: 'post', 132 | headers: { customHeader: 'customValue' }, 133 | body: '{"firstName": "Jake", "lastName": "Moxey"}', 134 | timeoutInterval: 10000 // timeout after 10 seconds 135 | sslPinning: { 136 | cert: 'cert-file-name', // cert file name without the `.cer` 137 | certs: ['cert-file-name-1', 'cert-file-name-2'], // optionally specify multiple certificates 138 | } 139 | }) 140 | .then(res => console.log(`We got your response! Response - ${res}`)) 141 | .catch(err => console.log(`Whoopsy doodle! Error - ${err}`)) 142 | ``` 143 | 144 | ### Using Callbacks 145 | ```javascript 146 | import pinch from 'react-native-pinch-new'; 147 | 148 | pinch.fetch('https://my-api.com/v1/endpoint', { 149 | method: 'post', 150 | headers: { customHeader: 'customValue' }, 151 | body: '{"firstName": "Jake", "lastName": "Moxey"}', 152 | timeoutInterval: 10000 // timeout after 10 seconds 153 | sslPinning: { 154 | cert: 'cert-file-name', // cert file name without the `.cer` 155 | certs: ['cert-file-name-1', 'cert-file-name-2'], // optionally specify multiple certificates 156 | } 157 | }, (err, res) => { 158 | if (err) { 159 | console.error(`Whoopsy doodle! Error - ${err}`); 160 | return null; 161 | } 162 | console.log(`We got your response! Response - ${res}`); 163 | }) 164 | ``` 165 | 166 | ### Skipping validation 167 | 168 | In original library, android did not support http. In order to achieve this, you only need to pass a valid http url. 169 | 170 | ```javascript 171 | import pinch from 'react-native-pinch-new'; 172 | 173 | pinch.fetch('https://my-api.com/v1/endpoint', { 174 | method: 'post', 175 | headers: { customHeader: 'customValue' }, 176 | body: '{"firstName": "Jake", "lastName": "Moxey"}', 177 | timeoutInterval: 10000 // timeout after 10 seconds 178 | ignore_ssl:true, // This can be used to ignore SSL pinning 179 | sslPinning: {} // omit the `cert` or `certs` key, `sslPinning` can be ommited as well 180 | }) 181 | ``` 182 | 183 | ## Response Schema 184 | ```javascript 185 | { 186 | bodyString: '', 187 | 188 | headers: {}, 189 | 190 | status: 200, 191 | 192 | statusText: 'OK' 193 | } 194 | ``` 195 | 196 | ## Testing 197 | 198 | ### With jest 199 | 200 | Using [fetch-mock](http://www.wheresrhys.co.uk/fetch-mock/) here, but nock or any other fetch polyfill would work. 201 | 202 | ```js 203 | # __mocks__/react-native-pinch-new.js 204 | import fetchMock from 'fetch-mock'; 205 | 206 | export default { 207 | fetch: fetchMock.sandbox(), // mock pinch's fetch with the sandbox version 208 | }; 209 | ``` 210 | 211 | ```js 212 | # __tests__/store.js 213 | import configureMockStore from 'redux-mock-store'; 214 | import thunk from 'redux-thunk'; 215 | import pinch from 'react-native-pinch-new'; // actually the sandbox from fetch-mock 216 | 217 | import { fetchFoos } from './path/to/store/actions'; 218 | 219 | jest.mock('react-native-pinch-new'); 220 | 221 | const middlewares = [thunk]; 222 | const mockStore = configureMockStore(middlewares); 223 | 224 | afterEach(() => { 225 | pinch.fetch.reset(); 226 | pinch.fetch.restore(); 227 | }); 228 | 229 | describe('fetchFoos', () => { 230 | it('creates FOO_BAR when fetching foos is done', () => { 231 | pinch.fetch.get(/^\/foos/, { foos: [] }); 232 | const store = mockStore(defaultState); 233 | 234 | return store.dispatch(fetchFoos()).then(() => { 235 | expect(store.getActions()).toEqual(expect.arrayContaining( 236 | [expect.objectContaining({ type: FOO_BAR })], 237 | )); 238 | }); 239 | }); 240 | }); 241 | ``` 242 | -------------------------------------------------------------------------------- /android/src/main/java/com/localz/RNPinch.java: -------------------------------------------------------------------------------- 1 | package com.localz; 2 | 3 | import android.os.AsyncTask; 4 | import android.util.Log; 5 | import android.content.pm.PackageManager; 6 | import android.content.pm.PackageInfo; 7 | import android.content.pm.ApplicationInfo; 8 | import android.content.pm.PackageManager.NameNotFoundException; 9 | 10 | import com.facebook.react.bridge.Arguments; 11 | import com.facebook.react.bridge.Callback; 12 | import com.facebook.react.bridge.ReactApplicationContext; 13 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 14 | import com.facebook.react.bridge.ReactMethod; 15 | import com.facebook.react.bridge.ReadableMap; 16 | import com.facebook.react.bridge.ReadableArray; 17 | import com.facebook.react.bridge.UnexpectedNativeTypeException; 18 | import com.facebook.react.bridge.WritableMap; 19 | 20 | import com.localz.pinch.models.HttpRequest; 21 | import com.localz.pinch.models.HttpResponse; 22 | import com.localz.pinch.utils.HttpUtil; 23 | import com.localz.pinch.utils.JsonUtil; 24 | 25 | import org.json.JSONException; 26 | import org.json.JSONObject; 27 | 28 | import java.io.IOException; 29 | import java.net.SocketTimeoutException; 30 | import java.net.ConnectException; 31 | import java.net.UnknownHostException; 32 | import java.security.KeyManagementException; 33 | import java.security.KeyStoreException; 34 | import java.security.NoSuchAlgorithmException; 35 | import java.security.cert.CertificateException; 36 | 37 | public class RNPinch extends ReactContextBaseJavaModule { 38 | 39 | private static final String OPT_METHOD_KEY = "method"; 40 | private static final String OPT_HEADER_KEY = "headers"; 41 | private static final String OPT_BODY_KEY = "body"; 42 | private static final String OPT_IGNORE_SSL_KEY = "ignore_ssl"; 43 | private static final String OPT_SSL_PINNING_KEY = "sslPinning"; 44 | private static final String OPT_TIMEOUT_KEY = "timeoutInterval"; 45 | 46 | private HttpUtil httpUtil; 47 | private String packageName = null; 48 | private String displayName = null; 49 | private String version = null; 50 | private String versionCode = null; 51 | 52 | public RNPinch(ReactApplicationContext reactContext) { 53 | super(reactContext); 54 | httpUtil = new HttpUtil(); 55 | try { 56 | PackageManager pManager = reactContext.getPackageManager(); 57 | packageName = reactContext.getPackageName(); 58 | PackageInfo pInfo = pManager.getPackageInfo(packageName, 0); 59 | ApplicationInfo aInfo = pManager.getApplicationInfo(packageName, 0); 60 | displayName = pManager.getApplicationLabel(aInfo).toString(); 61 | version = pInfo.versionName; 62 | versionCode = String.valueOf(pInfo.versionCode); 63 | } catch (NameNotFoundException nnfe) { 64 | System.out.println("RNAppInfo: package name not found"); 65 | } 66 | } 67 | 68 | @Override 69 | public String getName() { 70 | return "RNPinch"; 71 | } 72 | 73 | @ReactMethod 74 | public void fetch(String endpoint, ReadableMap opts, Callback callback) { 75 | new FetchTask(opts, callback).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, endpoint); 76 | } 77 | 78 | private class FetchTask extends AsyncTask { 79 | private ReadableMap opts; 80 | private Callback callback; 81 | 82 | public FetchTask(ReadableMap opts, Callback callback) { 83 | this.opts = opts; 84 | this.callback = callback; 85 | } 86 | 87 | @Override 88 | protected WritableMap doInBackground(String... endpoint) { 89 | 90 | try { 91 | WritableMap response = Arguments.createMap(); 92 | HttpRequest request = new HttpRequest(endpoint[0]); 93 | Boolean ignoressl = false; 94 | 95 | if (opts.hasKey(OPT_BODY_KEY)) { 96 | request.body = opts.getString(OPT_BODY_KEY); 97 | } 98 | if (opts.hasKey(OPT_METHOD_KEY)) { 99 | request.method = opts.getString(OPT_METHOD_KEY); 100 | } 101 | if (opts.hasKey(OPT_HEADER_KEY)) { 102 | request.headers = JsonUtil.convertReadableMapToJson(opts.getMap(OPT_HEADER_KEY)); 103 | } 104 | if (opts.hasKey(OPT_IGNORE_SSL_KEY)) { 105 | ignoressl = opts.getBoolean(OPT_IGNORE_SSL_KEY); 106 | request.ignore_ssl = opts.getBoolean(OPT_IGNORE_SSL_KEY); 107 | } 108 | if (opts.hasKey(OPT_SSL_PINNING_KEY) && !ignoressl) { 109 | if (opts.getMap(OPT_SSL_PINNING_KEY).hasKey("cert")) { 110 | String fileName = opts.getMap(OPT_SSL_PINNING_KEY).getString("cert"); 111 | request.certFilenames = new String[]{fileName}; 112 | } else if (opts.getMap(OPT_SSL_PINNING_KEY).hasKey("certs")) { 113 | ReadableArray certsStrings = opts.getMap(OPT_SSL_PINNING_KEY).getArray("certs"); 114 | String[] certs = new String[certsStrings.size()]; 115 | for (int i = 0; i < certsStrings.size(); i++) { 116 | certs[i] = certsStrings.getString(i); 117 | } 118 | request.certFilenames = certs; 119 | } 120 | } 121 | if (opts.hasKey(OPT_TIMEOUT_KEY)) { 122 | request.timeout = opts.getInt(OPT_TIMEOUT_KEY); 123 | } 124 | 125 | HttpResponse httpResponse = httpUtil.sendHttpRequest(request); 126 | 127 | response.putInt("status", httpResponse.statusCode); 128 | response.putString("statusText", httpResponse.statusText); 129 | response.putString("bodyString", httpResponse.bodyString); 130 | response.putMap("headers", httpResponse.headers); 131 | 132 | return response; 133 | }catch(SocketTimeoutException STE){ 134 | WritableMap er = Arguments.createMap(); 135 | er.putString("message", "The request timed out."); 136 | er.putInt("code", -1001); 137 | return er; 138 | }catch(ConnectException | UnknownHostException Ex){ 139 | WritableMap er = Arguments.createMap(); 140 | er.putString("message", Ex.toString()); 141 | er.putInt("code", -1003); 142 | return er; 143 | } 144 | catch(JSONException | IOException | UnexpectedNativeTypeException | KeyStoreException | CertificateException | KeyManagementException | NoSuchAlgorithmException e) { 145 | WritableMap er = Arguments.createMap(); 146 | er.putString("message", e.toString()); 147 | er.putInt("code", -998); 148 | return er; 149 | } 150 | } 151 | 152 | @Override 153 | protected void onPostExecute(WritableMap response) { 154 | 155 | if (response.hasKey("message")) { 156 | callback.invoke(response, null); 157 | } else { 158 | callback.invoke(null, response); 159 | } 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /RNPinch/RNPinch.m: -------------------------------------------------------------------------------- 1 | // 2 | // RNNativeFetch.m 3 | // medipass 4 | // 5 | // Created by Paul Wong on 13/10/16. 6 | // Copyright © 2016 Localz. All rights reserved. 7 | // 8 | 9 | #import "RNPinch.h" 10 | #import "RCTBridge.h" 11 | 12 | @interface RNPinchException : NSException 13 | @end 14 | @implementation RNPinchException 15 | @end 16 | 17 | // private delegate for verifying certs 18 | @interface NSURLSessionSSLPinningDelegate:NSObject 19 | 20 | - (id)initWithCertNames:(NSArray *)certNames; 21 | 22 | @property (nonatomic, strong) NSArray *certNames; 23 | 24 | @end 25 | 26 | @implementation NSURLSessionSSLPinningDelegate 27 | 28 | - (id)initWithCertNames:(NSArray *)certNames { 29 | if (self = [super init]) { 30 | _certNames = certNames; 31 | } 32 | return self; 33 | } 34 | 35 | - (NSArray *)pinnedCertificateData { 36 | NSMutableArray *localCertData = [NSMutableArray array]; 37 | for (NSString* certName in self.certNames) { 38 | NSString *cerPath = [[NSBundle mainBundle] pathForResource:certName ofType:@"der"]; 39 | if (cerPath == nil) { 40 | @throw [[RNPinchException alloc] 41 | initWithName:@"CertificateError" 42 | reason:@"Can not load certicate given, check it's in the app resources." 43 | userInfo:nil]; 44 | } 45 | [localCertData addObject:[NSData dataWithContentsOfFile:cerPath]]; 46 | } 47 | 48 | NSMutableArray *pinnedCertificates = [NSMutableArray array]; 49 | for (NSData *certificateData in localCertData) { 50 | [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)]; 51 | } 52 | return pinnedCertificates; 53 | } 54 | 55 | - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler { 56 | 57 | if ([[[challenge protectionSpace] authenticationMethod] isEqualToString:NSURLAuthenticationMethodServerTrust]) { 58 | NSString *domain = challenge.protectionSpace.host; 59 | SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust]; 60 | 61 | NSArray *policies = @[(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)]; 62 | 63 | SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies); 64 | // setup 65 | SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)self.pinnedCertificateData); 66 | SecTrustResultType result; 67 | 68 | // evaluate 69 | OSStatus errorCode = SecTrustEvaluate(serverTrust, &result); 70 | 71 | BOOL evaluatesAsTrusted = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed); 72 | if (errorCode == errSecSuccess && evaluatesAsTrusted) { 73 | NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust]; 74 | completionHandler(NSURLSessionAuthChallengeUseCredential, credential); 75 | } else { 76 | completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, NULL); 77 | } 78 | } else { 79 | completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, NULL); 80 | } 81 | } 82 | 83 | @end 84 | 85 | @interface RNPinch() 86 | 87 | @property (nonatomic, strong) NSURLSessionConfiguration *sessionConfig; 88 | 89 | @end 90 | 91 | @implementation RNPinch 92 | RCT_EXPORT_MODULE(); 93 | 94 | - (instancetype)init 95 | { 96 | self = [super init]; 97 | if (self) { 98 | self.sessionConfig = [NSURLSessionConfiguration ephemeralSessionConfiguration]; 99 | self.sessionConfig.HTTPCookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; 100 | } 101 | return self; 102 | } 103 | 104 | + (BOOL)requiresMainQueueSetup 105 | { 106 | return NO; 107 | } 108 | 109 | RCT_EXPORT_METHOD(fetch:(NSString *)url obj:(NSDictionary *)obj callback:(RCTResponseSenderBlock)callback) { 110 | NSURL *u = [NSURL URLWithString:url]; 111 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:u]; 112 | 113 | NSURLSession *session; 114 | if (obj) { 115 | if (obj[@"method"]) { 116 | [request setHTTPMethod:obj[@"method"]]; 117 | } 118 | if (obj[@"timeoutInterval"]) { 119 | [request setTimeoutInterval:[obj[@"timeoutInterval"] doubleValue] / 1000]; 120 | } 121 | if (obj[@"headers"] && [obj[@"headers"] isKindOfClass:[NSDictionary class]]) { 122 | NSMutableDictionary *m = [obj[@"headers"] mutableCopy]; 123 | for (NSString *key in [m allKeys]) { 124 | if (![m[key] isKindOfClass:[NSString class]]) { 125 | m[key] = [m[key] stringValue]; 126 | } 127 | } 128 | [request setAllHTTPHeaderFields:m]; 129 | } 130 | if (obj[@"body"]) { 131 | NSData *data = [obj[@"body"] dataUsingEncoding:NSUTF8StringEncoding]; 132 | [request setHTTPBody:data]; 133 | } 134 | } 135 | if (obj && obj[@"ignore_ssl"] && ![[obj objectForKey:@"ignore_ssl"] boolValue] && obj[@"sslPinning"] && obj[@"sslPinning"][@"cert"]) { 136 | NSURLSessionSSLPinningDelegate *delegate = [[NSURLSessionSSLPinningDelegate alloc] initWithCertNames:@[obj[@"sslPinning"][@"cert"]]]; 137 | session = [NSURLSession sessionWithConfiguration:self.sessionConfig delegate:delegate delegateQueue:[NSOperationQueue mainQueue]]; 138 | } else if (obj && obj[@"ignore_ssl"] && ![[obj objectForKey:@"ignore_ssl"] boolValue] && obj[@"sslPinning"] && obj[@"sslPinning"][@"certs"]) { 139 | // load all certs 140 | NSURLSessionSSLPinningDelegate *delegate = [[NSURLSessionSSLPinningDelegate alloc] initWithCertNames:obj[@"sslPinning"][@"certs"]]; 141 | session = [NSURLSession sessionWithConfiguration:self.sessionConfig delegate:delegate delegateQueue:[NSOperationQueue mainQueue]]; 142 | } else { 143 | session = [NSURLSession sessionWithConfiguration:self.sessionConfig]; 144 | } 145 | 146 | __block NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { 147 | if (!error) { 148 | dispatch_async(dispatch_get_main_queue(), ^{ 149 | NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response; 150 | NSInteger statusCode = httpResp.statusCode; 151 | NSString *bodyString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 152 | NSString *statusText = [NSHTTPURLResponse localizedStringForStatusCode:httpResp.statusCode]; 153 | 154 | NSDictionary *res = @{ 155 | @"status": @(statusCode), 156 | @"headers": httpResp.allHeaderFields, 157 | @"bodyString": bodyString, 158 | @"statusText": statusText 159 | }; 160 | callback(@[[NSNull null], res]); 161 | }); 162 | } else { 163 | dispatch_async(dispatch_get_main_queue(), ^{ 164 | NSInteger code = [error code]; 165 | callback(@[@{@"message":error.localizedDescription,@"code":@(code) }, [NSNull null]]); 166 | }); 167 | } 168 | }]; 169 | 170 | [dataTask resume]; 171 | } 172 | 173 | @end 174 | -------------------------------------------------------------------------------- /RNPinch.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | DA08C4AF1DAF39C600714E34 /* RNPinch.m in Sources */ = {isa = PBXBuildFile; fileRef = DA08C4AE1DAF39C600714E34 /* RNPinch.m */; }; 11 | DA08C4B01DAF39C600714E34 /* RNPinch.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = DA08C4AD1DAF39C600714E34 /* RNPinch.h */; }; 12 | /* End PBXBuildFile section */ 13 | 14 | /* Begin PBXCopyFilesBuildPhase section */ 15 | DA08C4A81DAF39C600714E34 /* CopyFiles */ = { 16 | isa = PBXCopyFilesBuildPhase; 17 | buildActionMask = 2147483647; 18 | dstPath = "include/$(PRODUCT_NAME)"; 19 | dstSubfolderSpec = 16; 20 | files = ( 21 | DA08C4B01DAF39C600714E34 /* RNPinch.h in CopyFiles */, 22 | ); 23 | runOnlyForDeploymentPostprocessing = 0; 24 | }; 25 | /* End PBXCopyFilesBuildPhase section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | DA08C4AA1DAF39C600714E34 /* libRNPinch.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNPinch.a; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | DA08C4AD1DAF39C600714E34 /* RNPinch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNPinch.h; sourceTree = ""; }; 30 | DA08C4AE1DAF39C600714E34 /* RNPinch.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNPinch.m; sourceTree = ""; }; 31 | /* End PBXFileReference section */ 32 | 33 | /* Begin PBXFrameworksBuildPhase section */ 34 | DA08C4A71DAF39C600714E34 /* Frameworks */ = { 35 | isa = PBXFrameworksBuildPhase; 36 | buildActionMask = 2147483647; 37 | files = ( 38 | ); 39 | runOnlyForDeploymentPostprocessing = 0; 40 | }; 41 | /* End PBXFrameworksBuildPhase section */ 42 | 43 | /* Begin PBXGroup section */ 44 | DA08C4A11DAF39C600714E34 = { 45 | isa = PBXGroup; 46 | children = ( 47 | DA08C4AC1DAF39C600714E34 /* RNPinch */, 48 | DA08C4AB1DAF39C600714E34 /* Products */, 49 | ); 50 | sourceTree = ""; 51 | usesTabs = 0; 52 | }; 53 | DA08C4AB1DAF39C600714E34 /* Products */ = { 54 | isa = PBXGroup; 55 | children = ( 56 | DA08C4AA1DAF39C600714E34 /* libRNPinch.a */, 57 | ); 58 | name = Products; 59 | sourceTree = ""; 60 | }; 61 | DA08C4AC1DAF39C600714E34 /* RNPinch */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | DA08C4AD1DAF39C600714E34 /* RNPinch.h */, 65 | DA08C4AE1DAF39C600714E34 /* RNPinch.m */, 66 | ); 67 | path = RNPinch; 68 | sourceTree = ""; 69 | }; 70 | /* End PBXGroup section */ 71 | 72 | /* Begin PBXNativeTarget section */ 73 | DA08C4A91DAF39C600714E34 /* RNPinch */ = { 74 | isa = PBXNativeTarget; 75 | buildConfigurationList = DA08C4B31DAF39C600714E34 /* Build configuration list for PBXNativeTarget "RNPinch" */; 76 | buildPhases = ( 77 | DA08C4A61DAF39C600714E34 /* Sources */, 78 | DA08C4A71DAF39C600714E34 /* Frameworks */, 79 | DA08C4A81DAF39C600714E34 /* CopyFiles */, 80 | ); 81 | buildRules = ( 82 | ); 83 | dependencies = ( 84 | ); 85 | name = RNPinch; 86 | productName = RNPinch; 87 | productReference = DA08C4AA1DAF39C600714E34 /* libRNPinch.a */; 88 | productType = "com.apple.product-type.library.static"; 89 | }; 90 | /* End PBXNativeTarget section */ 91 | 92 | /* Begin PBXProject section */ 93 | DA08C4A21DAF39C600714E34 /* Project object */ = { 94 | isa = PBXProject; 95 | attributes = { 96 | LastUpgradeCheck = 0800; 97 | ORGANIZATIONNAME = Localz; 98 | TargetAttributes = { 99 | DA08C4A91DAF39C600714E34 = { 100 | CreatedOnToolsVersion = 8.0; 101 | ProvisioningStyle = Automatic; 102 | }; 103 | }; 104 | }; 105 | buildConfigurationList = DA08C4A51DAF39C600714E34 /* Build configuration list for PBXProject "RNPinch" */; 106 | compatibilityVersion = "Xcode 3.2"; 107 | developmentRegion = English; 108 | hasScannedForEncodings = 0; 109 | knownRegions = ( 110 | en, 111 | ); 112 | mainGroup = DA08C4A11DAF39C600714E34; 113 | productRefGroup = DA08C4AB1DAF39C600714E34 /* Products */; 114 | projectDirPath = ""; 115 | projectRoot = ""; 116 | targets = ( 117 | DA08C4A91DAF39C600714E34 /* RNPinch */, 118 | ); 119 | }; 120 | /* End PBXProject section */ 121 | 122 | /* Begin PBXSourcesBuildPhase section */ 123 | DA08C4A61DAF39C600714E34 /* Sources */ = { 124 | isa = PBXSourcesBuildPhase; 125 | buildActionMask = 2147483647; 126 | files = ( 127 | DA08C4AF1DAF39C600714E34 /* RNPinch.m in Sources */, 128 | ); 129 | runOnlyForDeploymentPostprocessing = 0; 130 | }; 131 | /* End PBXSourcesBuildPhase section */ 132 | 133 | /* Begin XCBuildConfiguration section */ 134 | DA08C4B11DAF39C600714E34 /* Debug */ = { 135 | isa = XCBuildConfiguration; 136 | buildSettings = { 137 | ALWAYS_SEARCH_USER_PATHS = NO; 138 | CLANG_ANALYZER_NONNULL = YES; 139 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 140 | CLANG_CXX_LIBRARY = "libc++"; 141 | CLANG_ENABLE_MODULES = YES; 142 | CLANG_ENABLE_OBJC_ARC = YES; 143 | CLANG_WARN_BOOL_CONVERSION = YES; 144 | CLANG_WARN_CONSTANT_CONVERSION = YES; 145 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 146 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 147 | CLANG_WARN_EMPTY_BODY = YES; 148 | CLANG_WARN_ENUM_CONVERSION = YES; 149 | CLANG_WARN_INFINITE_RECURSION = YES; 150 | CLANG_WARN_INT_CONVERSION = YES; 151 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 152 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 153 | CLANG_WARN_UNREACHABLE_CODE = YES; 154 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 155 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 156 | COPY_PHASE_STRIP = NO; 157 | DEBUG_INFORMATION_FORMAT = dwarf; 158 | ENABLE_STRICT_OBJC_MSGSEND = YES; 159 | ENABLE_TESTABILITY = YES; 160 | GCC_C_LANGUAGE_STANDARD = gnu99; 161 | GCC_DYNAMIC_NO_PIC = NO; 162 | GCC_NO_COMMON_BLOCKS = YES; 163 | GCC_OPTIMIZATION_LEVEL = 0; 164 | GCC_PREPROCESSOR_DEFINITIONS = ( 165 | "DEBUG=1", 166 | "$(inherited)", 167 | ); 168 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 169 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 170 | GCC_WARN_UNDECLARED_SELECTOR = YES; 171 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 172 | GCC_WARN_UNUSED_FUNCTION = YES; 173 | GCC_WARN_UNUSED_VARIABLE = YES; 174 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 175 | MTL_ENABLE_DEBUG_INFO = YES; 176 | ONLY_ACTIVE_ARCH = YES; 177 | SDKROOT = iphoneos; 178 | }; 179 | name = Debug; 180 | }; 181 | DA08C4B21DAF39C600714E34 /* Release */ = { 182 | isa = XCBuildConfiguration; 183 | buildSettings = { 184 | ALWAYS_SEARCH_USER_PATHS = NO; 185 | CLANG_ANALYZER_NONNULL = YES; 186 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 187 | CLANG_CXX_LIBRARY = "libc++"; 188 | CLANG_ENABLE_MODULES = YES; 189 | CLANG_ENABLE_OBJC_ARC = YES; 190 | CLANG_WARN_BOOL_CONVERSION = YES; 191 | CLANG_WARN_CONSTANT_CONVERSION = YES; 192 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 193 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 194 | CLANG_WARN_EMPTY_BODY = YES; 195 | CLANG_WARN_ENUM_CONVERSION = YES; 196 | CLANG_WARN_INFINITE_RECURSION = YES; 197 | CLANG_WARN_INT_CONVERSION = YES; 198 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 199 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 200 | CLANG_WARN_UNREACHABLE_CODE = YES; 201 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 202 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 203 | COPY_PHASE_STRIP = NO; 204 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 205 | ENABLE_NS_ASSERTIONS = NO; 206 | ENABLE_STRICT_OBJC_MSGSEND = YES; 207 | GCC_C_LANGUAGE_STANDARD = gnu99; 208 | GCC_NO_COMMON_BLOCKS = YES; 209 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 210 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 211 | GCC_WARN_UNDECLARED_SELECTOR = YES; 212 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 213 | GCC_WARN_UNUSED_FUNCTION = YES; 214 | GCC_WARN_UNUSED_VARIABLE = YES; 215 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 216 | MTL_ENABLE_DEBUG_INFO = NO; 217 | SDKROOT = iphoneos; 218 | VALIDATE_PRODUCT = YES; 219 | }; 220 | name = Release; 221 | }; 222 | DA08C4B41DAF39C600714E34 /* Debug */ = { 223 | isa = XCBuildConfiguration; 224 | buildSettings = { 225 | HEADER_SEARCH_PATHS = ( 226 | "$(inherited)", 227 | "$(SRCROOT)/node_modules/react-native/React/**", 228 | "$(SRCROOT)/../react-native/React/**", 229 | ); 230 | OTHER_LDFLAGS = "-ObjC"; 231 | PRODUCT_NAME = "$(TARGET_NAME)"; 232 | SKIP_INSTALL = YES; 233 | }; 234 | name = Debug; 235 | }; 236 | DA08C4B51DAF39C600714E34 /* Release */ = { 237 | isa = XCBuildConfiguration; 238 | buildSettings = { 239 | HEADER_SEARCH_PATHS = ( 240 | "$(inherited)", 241 | "$(SRCROOT)/node_modules/react-native/React/**", 242 | "$(SRCROOT)/../react-native/React/**", 243 | ); 244 | OTHER_LDFLAGS = "-ObjC"; 245 | PRODUCT_NAME = "$(TARGET_NAME)"; 246 | SKIP_INSTALL = YES; 247 | }; 248 | name = Release; 249 | }; 250 | /* End XCBuildConfiguration section */ 251 | 252 | /* Begin XCConfigurationList section */ 253 | DA08C4A51DAF39C600714E34 /* Build configuration list for PBXProject "RNPinch" */ = { 254 | isa = XCConfigurationList; 255 | buildConfigurations = ( 256 | DA08C4B11DAF39C600714E34 /* Debug */, 257 | DA08C4B21DAF39C600714E34 /* Release */, 258 | ); 259 | defaultConfigurationIsVisible = 0; 260 | defaultConfigurationName = Release; 261 | }; 262 | DA08C4B31DAF39C600714E34 /* Build configuration list for PBXNativeTarget "RNPinch" */ = { 263 | isa = XCConfigurationList; 264 | buildConfigurations = ( 265 | DA08C4B41DAF39C600714E34 /* Debug */, 266 | DA08C4B51DAF39C600714E34 /* Release */, 267 | ); 268 | defaultConfigurationIsVisible = 0; 269 | defaultConfigurationName = Release; 270 | }; 271 | /* End XCConfigurationList section */ 272 | }; 273 | rootObject = DA08C4A21DAF39C600714E34 /* Project object */; 274 | } 275 | --------------------------------------------------------------------------------