[![N|Solid](https://assets.cloudwise.com/content/dam/cloudwise/misc/cloudwise_web.png)](https://cloudwise.com)
# Cloudwise React Native Plugin
The Cloudwise React Native plugin helps auto-instrument your React Native app with Cloudwise OneAgent for Android and iOS and also provides an API to add manual instrumentation. It is compatible with raw, ejected React Native projects, which means it works with Expo Kit, but not with Expo.

If you want to start using this plugin and are not a Cloudwise customer yet, head to [cloudwise.com](https://cloudwise.com) and sign up for a free trial. For an intro you can also check out [our announcement blog post](https://www.cloudwise.com/news/blog/enhance-user-experience-with-full-insight-into-your-react-native-apps/).

## Supported features
* Auto-instrumentation using OneAgent for Android and iOS
  * User actions for application start and native controls
  * Web requests
  * Crashes
* React-native Auto-instrumentation
  * User actions for touches (Touchables, Buttons, Pickers, RefreshControl and lifecycle events such as render(), didMount() and didUpdate())
  * Reporting React Native errors
* Manual instrumentation
  * Typescript bindings to add manual instrumentation

## Requirements
* React Native >= 0.48
* Gradle >= 5.0 ([How to upgrade?](#gradleUpdate))
* React Native instrumentation => ES6 Classes
* For Android users: Minimum SDK version 15
* For iOS users: Minimum iOS 8

## Quick Setup

1. [Install plugin](#installPlugin)
2. [Register Cloudwise transformer](#transformer)
3. [Setup configuration](#installationCloudwise)
4. [Build and run your app](#buildAndRun)

## Advanced topics
* [Manual instrumentation](#usageMobileAgent)
  * [Create Manual Action](#manualAction)
  * [Report Values](#reportValues)
  * [Identify User](#identifyUser)
  * [Crash Reporting](#crashreporting)
  * [Data Collection](#datacollection)
  * [Report GPS Position](#reportgps)
  * [Platform independent reporting](#platform)
* [Custom arguments for instrumentation script](#customArguments)
* [Manual adding iOS Agent to project](#manualIosAgent)
* [Configuration structure](#configurationStructure)
  * [Lifecycle modes](#lifecycleModes)
  * [Define build stages](#buildStages)
  * [User Opt In Mode](#useroptin)
  * [Agent debug logs](#agentDebugLogs)
* [How does Cloudwise determine the user action name?](#userActionNaming)
* [Custom transformer for multiple transformer](#customTransformer)
* [Upgrading project to Gradle 5](#gradleUpdate)

## Troubleshooting
* [Documentation](#documentation)
* [Known issues](#trouble)
* [Report bug / Get support](#supportcase)
* [Changelog](#changelog)
<br/><br/>

# Quick Setup

## <a name="installPlugin"></a>1. Install the plugin
1. Install the plugin by calling:
    -  => RN 0.60.0 : `npm install @cloudwise/react-native-plugin` 
    -  < RN 0.60.0 : `react-native install @cloudwise/react-native-plugin`. 
2. **iOS only :** If you use pods, you need to go into your `ios` directory and execute `pod install` to install the new Cloudwise dependecy to your xCode project. 
  
### Troubleshooting
- Expo-Kit only: The installation script might not be triggered automatically. You can call it manually by running `node ./node_modules/@cloudwise/react-native-plugin/scripts/install.js` (You will notice this when the `npm run instrumentCloudwise` call is not available.).
- If for some reason (e.g. seperate native projects) `react-native link` doesn't work as expected, [manually add the iOS agent to your project](#manualIosAgent).

## <a name="installPlugin"></a>2. Register the Cloudwise transformer

Depending on your React Native version, you will need to use a different way to register the transformer. If you don't know the version, enter `react-native --version` in your terminal.

The following configuration must be added. Any configuration that is already in the file can remain. If you already have a registered transformer, [create a custom transformer](#customTransformer) that is calling several transformers. 

#### RN >= 0.57

In your project's root directory, create or extend (>= RN 0.59) `metro.config.js` / (>= RN 0.57 - 0.58) `rn-cli.config.js` so that it contains the `transformer.babelTransformerPath` property:

```js
module.exports = {
  transformer: {
    babelTransformerPath: require.resolve('@cloudwise/react-native-plugin/lib/cloudwise-transformer')
  },

  reporter: require("@cloudwise/react-native-plugin/lib/cloudwise-reporter")
};
```

#### RN < 0.57

Add this to your `rn-cli.config.js` (create one if you haven't already):

```js
module.exports = {
  getTransformModulePath() {
    return require.resolve('@cloudwise/react-native-plugin/lib/cloudwise-transformer');
  },
  getSourceExts() {
    // Or the extension you need, e.g. ts 
    return ['js'];
  }
}
```

## <a name="installationCloudwise"></a>3. Setup cloudwise.config.js

> **Note**: If you are upgrading from a previous version of this plugin, you'll notice that the file format has changed. Your old configuration is still available in `cloudwise.config` and you have to copy your values to the new `cloudwise.config.js`.

Define a mobile app in Cloudwise and open the Mobile app instrumentation settings. In the settings you will see a snippet for Android and iOS. Open the `cloudwise.config.js` in the root directory of your project that was already created by the npm install script and copy the snippets in there. You will see that the `cloudwise.config.js` already contains a default snippet for Android and iOS, but without correct values.  

> **Note**: Define the components that you want to see lifecycle instrumented ([example](#lifecycle)). This is important as you will only see Application startup and Touches out of the box.

For more details about the configuration, see [Advanced topics](#configurationStructure).

## <a name="installPlugin"></a>4. Build and run your app

1. **Only RN < 0.60**: Execute `npm run instrumentCloudwise` or `react-native instrument-cloudwise` in the root of your React Native project. This will configure both Android and iOS projects with the settings from `cloudwise.config.js`. You can use the same [custom arguments](#customArguments) as mentioned above.

2. Use `react-native run-android` or `react-native run-ios` to rebuild and run your app. Specify custom paths via [custom arguments.](#customArguments).

# Advanced topics

## <a name="usageMobileAgent"></a>Manual instrumentation

To use the API of the React Native plugin, import the API:

```js
import { Cloudwise, Platform } from '@cloudwise/react-native-plugin';
```

### <a name="manualAction"></a>Create manual actions

To create a manual action named `"MyButton tapped"`, use the following code. The *leaveAction* closes the action again. To report values for this action before closing, see [Report Values](#reportValues).

```js
let myAction = Cloudwise.enterAction("MyButton tapped");
//Perform the action and whatever else is needed.
myAction.leaveAction();
```

### <a name="manualAction"></a>Create manual sub actions

You can create a single manual action as well as sub actions. The `MyButton Sub Action` is automatically put under the `MyButton tapped`. As long as `MyButton tapped` is open, it gathers all the web requests.

```js
let myAction = Cloudwise.enterAction("MyButton tapped");
let mySubAction = Cloudwise.enterAction("MyButton Sub Action");
//Perform the action and whatever else is needed.
mySubAction.leaveAction();
myAction.leaveAction();
```

### <a name="reportValues"></a>Report values

For any open action you can report certain values. The following API is available for action:

```ts
reportDoubleValue(valueName: string, value: number, platform?: Platform): void
reportError(errorName: string, errorCode: number, platform?: Platform): void
reportEvent(eventName: string, platform?: Platform): void
reportIntValue(valueName: string, value: number, platform?: Platform): void
reportStringValue(valueName: string, value: string, platform?: Platform): void
```

To report a string value, use the following:

```js
let myAction = Cloudwise.enterAction("MyButton tapped");
myAction.reportStringValue("ValueName", "ImportantValue");
myAction.leaveAction();
```

If you look at the API calls, you will see the optional parameter `platform?: Platform`. This parameter offers the possibility to report values only for a specific platform. to know more, see [Platform independent reporting](#platform).

### <a name="identifyUser"></a>Identify a user

You can identify a user and tag the current session with a name by making the following call:

```js
Cloudwise.identifyUser("User XY");
```

### <a name="crashreporting"></a>Crash reporting

Crash reporting is enabled by default. The Mobile agent captures all unhandled exceptions/errors and immediately sends the crash report to the server. With this API you can activate or deactivate crash reporting. To change this behaviour via the API, enable/activate [`userOptIn`](#useroptin) and set the [privacy settings](#datacollection).

```ts
async isCrashReportingOptedIn(platform?: Platform): Promise<boolean>
setCrashReportingOptedIn(crashReporting: boolean, platform?: Platform): void
```

### <a name="datacollection"></a>Data collection

The privacy API methods allow you to dynamically change the data-collection level based on the individual preferences of your end users. Each end user can select from three data-privacy levels:

```ts
export enum DataCollectionLevel {
    Off, User, Performance
}
```

1. Off: Native Agent doesn't capture any monitoring data.
2. Performance: Native Agent captures only anonymous performance data. Monitoring data that can be used to identify individual users, such as user tags and custom values, aren't captured.
3. User: Native Agent captures both performance and user data. In this mode, Native Agent recognizes and reports users who re-visit in future sessions.

The API to get and set the current level looks like this:

```ts
async getDataCollectionLevel(platform?: Platform): Promise<DataCollectionLevel>
setDataCollectionLevel(dataCollectionLevel: DataCollectionLevel, platform?: Platform): void
```

### <a name="gspreport"></a>Report GPS Location

You can report latitude and longitude and specify an optional platform. 

```ts
setGPSLocation(latitude: number, longitude: number, platform?: Platform): void
```

### <a name="platform"></a>Platform independent reporting

You probably noticed that each method has an additional *optional* parameter named `platform` of type `Platform`. You can use this to only trigger manual instrumentation for a specific OS. The available values are: `Platform.Ios` and `Platform.Android`. Default is that it will work on any platform. Otherwise it is passed *only* to the relevant OS. For example:
 * Passing to **iOS** only:
```js
let myAction = Cloudwise.enterAction("MyButton tapped", Platform.Ios);
//Perform the action and whatever else is needed.
myAction.leaveAction("ios"); 
```
 
 * Passing to **Android** only:
```js
let myAction = Cloudwise.enterAction("MyButton tapped", Platform.Android);
//Perform the action and whatever else is needed.
myAction.leaveAction("android"); 
```
 
 * Passing to **both**:
```js
let myAction = Cloudwise.enterAction("MyButton tapped");
//Perform the action and whatever else is needed.
myAction.leaveAction(); 
```

## <a name="customArguments"></a>Custom arguments for instrumentation script

Our scripts assumes that the usual React Native project structure is given. The following arguments can be specified for our instrumentation script if the project structure is different.

* `gradle=C:\MyReactAndroidProject\build.gradle`: The location of the root build.gradle file. We will assume that the other gradle file resides in `/app/build.gradle`. This will add the whole agent dependencies automatically for you and will update the configuration.
* `plist=C:\MyReactIOSProject\projectName\info.plist`: Tell the script where your info.plist file is. The plist file is used for updating the configuration for the agent. 
* `config=C:\SpecialFolderForCloudwise\cloudwise.config.js`: If you have not got your config file in the root folder of the React Native project but somewhere else.

Examples:

 * RN 60.0:
```
react-native run-android config=C:\SpecialFolderForCloudwise\cloudwise.config.js --port=2000
```

> **Note:** that custom arguments must not be prefixed with -- !

## <a name="manualIosAgent"></a>Manually adding iOS OneAgent to a project

Adding the iOS agent manually depends on the availabilty of support for CocoaPods. 

### With CocoaPods support
Insert the following in your Podfile:

```
pod 'react-native-cloudwise', :path => '../node_modules/@cloudwise/react-native-plugin'
```

### Without CocoaPods support
1. Open your project in Xcode.
2. Run open `node_modules/@cloudwise/react-native-plugin/ios`.
3. Drag `CloudwiseRNBridge.xcodeproj` into your Libraries group.
4. Select your main project in the navigator to bring up settings.
5. Under Build Phases expand the Link Binary With Libraries header.
6. Scroll down and click + to add a library.
7. Find and add `libRNCloudwise.a` under the Workspace group.
8. ⌘+B

## <a name="configurationStructure"></a>Structure of the `cloudwise.js` file
The configuration is structured in the following way:

```js
module.exports = {
    react : {
      // Configuration for React Native instrumentation
    },
    android : {
      // Configuration for Android auto instrumentation
    },
    ios : {
      // Configuration for iOS auto instrumentation
    }
}
```

### React block

The `react` configuration block contains all settings regarding the react instrumentation. The following options are available:

#### Input

```js
react : {
  input : {
    instrument(filename) => {
      return true;
    }
  }
}
```

This instrument function expects you to return `true` or `false`. In this case, all files are instrumented to capture user input. 

#### Lifecycle <a name="lifecycle"></a>

```js
react : {
  lifecycle : {
    includeUpdate : false,
    instrument(filename) => {
      // This will only capture inputs in files in the path src/screens/
      return filename.startsWith(require('path').join('src', 'screens'));
    }
  }
}
```

The instrument function expects you to return `true` or `false`. In this case, all files in the `src/screens/` folder are instrumented to capture user input.

**Note:** it is important that you input all files here where you wish lifecycle instrumentation. Probably this should contain all your "real" screens. If you return `true` for this function, all lifecycle events will be reported, which can be a lot in React Native.

#### <a name="debugMode"></a>Debug mode

```js
react: {
  debug: true
}
```

This activates the debug mode. You will get more console output during instrumentation and at runtime.

### Android block

The Android block is a wrapper for the Android configuration you find in the WebUI (in the Mobile Application Settings). Copy the content into the following block:

```js
android : {
  config : `CONTENT_OF_ANDROID_CONFIG`
}
```

The content of the `config` block is directly copied to the Gradle file. To know more about the possible configuration options, see the [DSL documentation](https://www.cloudwise.com/support/doc/javadoc/oneagent/android/gradle-plugin/dsl/) of our Gradle plugin.

### iOS block

The iOS block is a wrapper for the iOS configuration you find in the WebUI (in the Mobile Application Settings). Copy the content into the following block:

```js
ios : {
  config : `CONTENT_OF_IOS_CONFIG`
}
```

The content of the `config` block is directly copied to the `plist` file. Therefore, you can use every setting that is possible and you find in the official Mobile Agent documentation.

## <a name="buildStages"></a>Define build stages in cloudwise.config.js

If you have several stages such as debug, QA, and production, you probably want to seperate them and let them report in different applications. This can be done with two different approaches:

1. Create several cloudwise.config.js (e.g. cloudwise.config.prod.js) and pass those configuration files via [arguments](#customArguments) in the CLI.
2. Use the configuration options which are available through Gradle and XCode. (Described below)

> **Note:** Option 1 has the drawback that you always need to perform the configuration step before a build as you are basically replacing the configuration all the time. So if you made a debug build and want to do a production build, which is reporting to a different environment or has different options, you need to perform `npm run instrumentCloudwise` (Or if you use RN 0.60+ this happens automatically with `react-native run-android` or `react-native run-ios`).

### Android

In Android, you can enter all the information in the config file. The following *cloudwise {}* block must be inserted into the android *config* variable in your cloudwise.config.js file.

```js
android : {
  config : `
    cloudwise {
      configurations {
        dev {
            variantFilter "Debug" // build type name is upper case because a product flavor is used
            // other variant-specific properties
        }
        demo {
            variantFilter "demo" // the first product flavor name is always lower case
            // other variant-specific properties
        }
        prod {
            variantFilter "Release" // build type name is upper case because a product flavor is used
            // other variant-specific properties
        }
      }
    }
  `
}
```

This will result in the following:

```
> Task :app:printVariantAffiliation
Variant 'demoDebug' will use configuration 'dev'
Variant 'demoRelease' will use configuration 'demo'
Variant 'paidDebug' will use configuration 'dev'
Variant 'paidRelease' will use configuration 'prod'
```

In all these blocks, you can define different application IDs and even use a different environment.

### iOS

In iOS, you can define some variables in the cloudwise.config.js file. These variables must then be inserted in a prebuild script. The following properties must be inserted into the iOS *config* variable in your cloudwise.config.js file.

```js
ios: {
  config: `
  <key>DTXApplicationID</key>
  <string>\${APPLICATION_ID}</string>
  <key>DTXBeaconURL</key>
  <string>Your Beacon URL</string>
  `
}
```

The variable ${APPLICATION_ID} must then be inserted with a prebuild script. Make sure to use \ in front of the ${...}, because if not JavaScript thinks you are trying to insert a variable into the String. For more information, see https://medium.com/@andersongusmao/xcode-targets-with-multiples-build-configuration-90a575ddc687.

## <a name="useroptin"></a>User opt-in mode

Specifies if the user has to opt-in for being monitored. When enabled, you must specify the privacy setting. For more information, see the [API section](#datacollection).

### Android

```js
android: {
  config: `
    cloudwise {
      configurations {
        defaultConfig {
          autoStart{
            ...
          }
          userOptIn true
        }
      }
    }
  `
}
```

### iOS

```js
ios: {
  config: `
  <key>DTXUserOptIn</key>
  </true>
  `
}
```

## <a name="agentDebugLogs"></a>Native OneAgent debug logs

If the instrumentation runs through and your application starts but you see no data, you probably need to dig deeper to find out why the OneAgents aren't sending any data. Opening up a support ticket is a great idea, but gathering logs first is even better. 

### Android

Add the following configuration snippet to your other configuration in cloudwise.config.js right under the autoStart block (the whole structure is visible, so you know where the config belongs) and run *npm run instrumentCloudwise*:

```js
android: {
  config: `
    cloudwise {
      configurations {
        defaultConfig {
          autoStart{
            ...
          }
          debug.agentLogging true
        }
      }
    }
  `
}
```

### iOS

Add the following configuration snippet to your other configuration in cloudwise.config.js (the whole structure is visible, so you know where the config belongs) and run *npm run instrumentCloudwise*:

```js
ios: {
  config: `
  <key>DTXLogLevel</key>
  <string>ALL</string>
  `
}
```

## <a name="userActionNaming"></a>How does Cloudwise determine the user action name?
* React views
  * displayName: Use the displayName property to check if React views have a display name set 
  * class name: If the display name is not available, the class name is used by taking the property name from the constructor
* Touchables
  * Accessibility label
  * If both are not set, it will search for an inner text
  * If it is an Image Button, it will search for a source
* Buttons
  * Button Title
  * Accessibility label
  * If it is an Image Button, it will search for a source
  * If it finds nothing, it will search for an inner text

If you minify your React Native code, use the keep_classname setting to preserve the class name.

## <a name="customTransformer"></a>Customer transformer for multiple transformers

If you want to register the Cloudwise transformer in your configuration and you already have a transformer in place, change the upstreaming transformer for the Cloudwise transformer.

This can be done via a configuration value in the `cloudwise.config.js` :

```js
// The `...` only indicates that there are other values as well, but we've omitted them in this example.
module.exports = {
    react : {
        upstreamTransformer: require.resolve('customTransformerLib/myTransformer'),
        ...
    },
    ...       
}
```

## <a name="gradleUpdate"></a>Updating to Gradle 5

Updating Gradle only affects your Android build. To update your project to Gradle 5, modify the following 3 files in your Android folder. 

- `ProjectFolder\android\gradle\wrapper\gradle-wrapper.properties` Update the distributionUrl to get a higher gradle version.

```
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
```

- `ProjectFolder\android\build.gradle` Update the version of your Android gradle plugin (here we updated to 3.4.0) as Gradle 5 needs a higher one. To get the newer versions, add `google()` in your repositories. Example of a build.gradle file:

```
buildscript {
    repositories {
        google()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.4.0'
    }
}

allprojects {
    repositories {
        google()
        mavenLocal()
        jcenter()
        maven {
            url "$rootDir/../node_modules/react-native/android"
        }
    }
}
```

- `ProjectFolder\android\app\build.gradle` This depends on how old your React Native project really is. You must change your used buildTools, compileSdkVersion, targetSdkVersion and support libaries. Older build.gradle files might look similar to this with unimportant parts removed to make the snippet smaller:

```
...

apply from: "../../node_modules/react-native/react.gradle"

...

android {
    compileSdkVersion 28
    buildToolsVersion "28.0.3"

    defaultConfig {
        minSdkVersion 16
        targetSdkVersion 28

        ...
    }

    ...
}

dependencies {
    compile "com.android.support:appcompat-v7:28.0.0"
    compile "com.facebook.react:react-native:+" 
}

...

```

## <a name="documentation"></a>Cloudwise documentation
The OneAgent for Android and iOS documentation is available at the following locations:
  - Android: https://www.cloudwise.com/support/help/setup-and-configuration/oneagent/android/
  - iOS: https://www.cloudwise.com/support/help/setup-and-configuration/oneagent/ios/

## <a name="trouble"></a>Troubleshooting and applicable restrictions
To resolve problems with the plugin, first look at creating logs and identify what went wrong. The logs can be found in the plugins folder of your React Native project (usually `node_modules/@cloudwise/react-native-plugin/logs`). 

* An error such as `CloudwiseNative.PLATFORM_ANDROID is null` indicates that the linking of the native library didn't work correctly. Often, React Native is unable to link correctly. Simply unlink (`react-native unlink`) and link (`react-native link`) again and the error should be gone.
* `Missing property DTXApplicationID` indicates that there is no configuration available. Ensure that you've called `npm run updateConfiguration` at least once.
* If you change your project to pods when you have already installed the plugin, duplicate symbols are generated because of the already linked library. Remove the module reference manually from your project. 

## <a name="supportcase"></a>Report a bug or open a support case

If you are struggling with a problem, submit a support ticket to Cloudwise (support.cloudwise.com) and provide the following details:
* Logs from the [native agent](#agentDebugLogs)
* Logs from `node_modules/@cloudwise/react-native-plugin/logs`
* Your cloudwise.config.js file
<br/><br/>
---
## <a name="changelog"></a>Changelog

1.202.0
* Startup configuration now only written once
* Fixed usage of displayName for component action creation

1.201.0
* Improved exception handling

1.200.0
* .TSX transformation fixed

1.198.0
* Podspec Update

1.192.2
* Fix for Installation Script not executed
* RN >= 0.62 Touchables support
* [Multi-Transformer support](#customTransformer)
* Updated & fixed .d.ts file
* Fixed NPE in text identification of Touchables
* Crashes are now reported in the overview
* iOS Webrequests are now linked with actions
* Fix for custom arguments on run-ios and run-android

0.186.0
* Fixed instrumentation (files were skipped)
* Changed [Configuration](#configurationStructure) format
* Android: Switched to JCenter repository
* Applying configuration automatically (>= RN 0.60)
* Updated documenation for manual instrumentation
* Fixed problem with default config and beaconUrl
* Improved text identification of Touchables
* ImageButtons and Icons will now be reported
* Improved logic for plist file insertion

0.182.2
* MacOS: Fixed directory creation issue

0.182.1
* Fixed Typescript Parsing
* Fixed Decorator Parsing
* Fixed directory issue with older node version

0.181.1
* Picker & Swipe to Refresh instrumented
* Dynamic Imports/Requires now supported
* Fixed iOS Bug with reportErrorWithStacktrace

0.179.1
* Made Plugin compatible with RN AutoLinking
* Improved instrumentation of require & imports
* Fixed Button instrumentation 
* Improved Text identification of Touchable
* Webrequest linking (Android only)
* Auto User action creation (Android only)
* Report Stacktrace via Error API (Android & iOS)
* Uninstall process now removes everything
* Modifying SourceMap, Debugging now possible
* Fixed configuration issue with npm install

0.174.0
* Switching to new Android instrumentation
* Added options to filter instrumentation

0.172.0
* Error reporting through auto instrumentation
* [Debug message](#debugOutput) output in console

0.171.0
* Added auto instrumentation for React classes

0.168.0
* Initial Beta Release
