@dynatrace/react-native-plugin 2.305.1 → 2.309.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -10
- package/android/build.gradle +3 -1
- package/android/src/main/java/com/dynatrace/android/agent/DynatraceAppStartModule.kt +128 -0
- package/android/src/main/java/com/dynatrace/android/agent/DynatraceInternalModule.kt +22 -0
- package/android/src/main/java/com/dynatrace/android/agent/DynatraceRNBridgeImpl.kt +411 -0
- package/android/src/main/java/com/dynatrace/android/agent/DynatraceReactPackage.kt +54 -0
- package/android/src/main/java/com/dynatrace/android/agent/DynatraceUtils.kt +86 -0
- package/android/src/main/java/com/dynatrace/android/agent/model/AppStartMeasurement.kt +15 -0
- package/android/src/main/java/com/dynatrace/android/agent/model/AppStartMeasurementType.kt +18 -0
- package/android/src/new/java/com/dynatrace/android/agent/DynatraceRNBridge.kt +228 -0
- package/android/src/old/java/com/dynatrace/android/agent/DynatraceRNBridge.kt +258 -0
- package/files/plugin.gradle +1 -1
- package/index.js +10 -10
- package/instrumentation/DynatraceInstrumentation.js +1 -0
- package/{lib → instrumentation/jsx}/CreateElement.js +6 -5
- package/{lib/instrumentor/base → instrumentation/jsx}/ElementHelper.js +14 -12
- package/{lib/jsx-dev-runtime.js → instrumentation/jsx/JsxDevRuntime.js} +10 -19
- package/instrumentation/jsx/JsxRuntime.js +63 -0
- package/instrumentation/jsx/JsxRuntimeUtil.js +31 -0
- package/instrumentation/jsx/components/ClassComponent.js +49 -0
- package/instrumentation/jsx/components/ComponentUtil.js +33 -0
- package/instrumentation/jsx/components/FunctionalComponent.js +20 -0
- package/{lib/instrumentor/base → instrumentation/jsx/components}/Picker.js +1 -1
- package/{lib/instrumentor/base → instrumentation/jsx/components}/RefreshControl.js +1 -1
- package/{lib/instrumentor/base → instrumentation/jsx/components}/Switch.js +1 -1
- package/{lib/instrumentor/base → instrumentation/jsx/components}/Touchable.js +5 -5
- package/instrumentation/libs/community/Picker.InstrInfo.js +20 -0
- package/{lib → instrumentation/libs}/community/Picker.js +1 -1
- package/instrumentation/libs/community/gesture-handler/Touchables.InstrInfo.js +23 -0
- package/{lib → instrumentation/libs}/community/gesture-handler/Touchables.js +1 -1
- package/{lib → instrumentation/libs}/react-native/RefreshControl.InstrInfo.js +2 -2
- package/{lib → instrumentation/libs}/react-native/RefreshControl.js +5 -5
- package/{lib → instrumentation/libs}/react-native/Switch.InstrInfo.js +2 -2
- package/{lib → instrumentation/libs}/react-native/Switch.js +5 -5
- package/instrumentation/libs/react-native/Touchables.InstrInfo.js +25 -0
- package/{lib → instrumentation/libs}/react-native/Touchables.js +1 -1
- package/{lib → instrumentation/libs}/react-navigation/ReactNavigation.js +6 -5
- package/{lib/instrumentor → instrumentation}/model/Types.js +1 -14
- package/instrumentation/model/TypesUtil.js +17 -0
- package/ios/DynatraceRNBridge.h +10 -1
- package/ios/DynatraceRNBridge.mm +152 -0
- package/jsx-dev-runtime.js +1 -1
- package/jsx-runtime.js +1 -1
- package/lib/core/Application.js +20 -0
- package/lib/{instrumentor/base → core}/Dynatrace.js +57 -48
- package/lib/{instrumentor/base → core}/DynatraceAction.js +23 -22
- package/lib/core/DynatraceInternal.js +46 -0
- package/lib/{instrumentor/base → core}/DynatraceRootAction.js +5 -4
- package/lib/{instrumentor/base → core}/DynatraceWebRequestTiming.js +6 -5
- package/lib/{instrumentor/base → core}/ErrorHandler.js +6 -5
- package/lib/{instrumentor/base → core}/configuration/Configuration.js +1 -1
- package/lib/{instrumentor/base → core}/configuration/ConfigurationDefaults.js +1 -2
- package/lib/{instrumentor/base → core}/configuration/ConfigurationHandler.js +2 -2
- package/lib/{instrumentor/base → core}/configuration/ManualStartupConfiguration.js +1 -1
- package/lib/core/logging/ConsoleLogger.js +27 -0
- package/lib/core/logging/LogLevel.js +12 -0
- package/lib/dynatrace-transformer.js +1 -1
- package/lib/next/Dynatrace.js +86 -0
- package/lib/next/DynatraceSecondGenForwarder.js +17 -0
- package/lib/next/IDynatrace.js +2 -0
- package/lib/next/IDynatraceForwarder.js +2 -0
- package/lib/next/appstart/AppStartObserver.js +32 -0
- package/lib/next/appstart/AppStartType.js +38 -0
- package/lib/next/events/EventCreator.js +77 -0
- package/lib/next/events/EventPipeline.js +57 -0
- package/lib/next/events/EventTimestamp.js +24 -0
- package/lib/next/events/ViewInfoCreator.js +27 -0
- package/lib/next/events/modifier/BaseDataEventModifier.js +31 -0
- package/lib/next/events/modifier/EventLimitation.js +69 -0
- package/lib/next/events/modifier/EventModifierUtil.js +88 -0
- package/lib/next/events/modifier/IEventModifier.js +2 -0
- package/lib/next/events/modifier/ModifyEventValidation.js +187 -0
- package/lib/next/events/modifier/NonFiniteNumbersModifier.js +50 -0
- package/lib/next/events/modifier/SendEventValidation.js +84 -0
- package/lib/next/events/spec/EventFieldKeysEnum.js +2 -0
- package/lib/next/events/spec/EventSpecContstants.js +46 -0
- package/lib/next/events/spec/IAppStartEvent.js +2 -0
- package/lib/next/events/spec/ICrashEvent.js +2 -0
- package/lib/next/events/spec/IErrorCodeEvent.js +2 -0
- package/lib/next/events/spec/IErrorExceptionEvent.js +2 -0
- package/lib/next/events/spec/IReactNativeEvent.js +2 -0
- package/lib/next/events/spec/IRumEvent.js +2 -0
- package/lib/next/provider/ITimestampProvider.js +2 -0
- package/lib/next/provider/TimestampProvider.js +10 -0
- package/package.json +51 -40
- package/react-native-dynatrace.podspec +1 -1
- package/src/lib/{instrumentor/base → core}/interface/NativeDynatraceBridge.ts +10 -0
- package/typings/react-native-dynatrace.d.ts +0 -1
- package/android/src/main/java/com/dynatrace/android/agent/DynatraceRNBridgeImpl.java +0 -362
- package/android/src/main/java/com/dynatrace/android/agent/DynatraceReactPackage.java +0 -67
- package/android/src/main/java/com/dynatrace/android/agent/DynatraceUtils.java +0 -76
- package/android/src/main/java/com/dynatrace/android/agent/PrivateDTBridge.java +0 -28
- package/android/src/new/java/com/dynatrace/android/agent/DynatraceRNBridge.java +0 -187
- package/android/src/old/java/com/dynatrace/android/agent/DynatraceRNBridge.java +0 -189
- package/lib/community/Picker.InstrInfo.js +0 -20
- package/lib/community/gesture-handler/Touchables.InstrInfo.js +0 -56
- package/lib/instrumentor/DynatraceInstrumentation.js +0 -1
- package/lib/instrumentor/base/Application.js +0 -16
- package/lib/instrumentor/base/DynatraceInternal.js +0 -34
- package/lib/instrumentor/base/Logger.js +0 -16
- package/lib/instrumentor/base/model/LogLevel.js +0 -17
- package/lib/jsx-runtime.js +0 -69
- package/lib/react/Component.js +0 -90
- package/lib/react-native/Touchables.InstrInfo.js +0 -60
- /package/{lib/instrumentor/base/interface → instrumentation/jsx}/IDynatraceProperties.js +0 -0
- /package/{lib → instrumentation/libs}/community/gesture-handler/index.js +0 -0
- /package/{lib → instrumentation/libs}/gesture-handler.js +0 -0
- /package/{lib → instrumentation/libs}/react-native/index.js +0 -0
- /package/{lib → instrumentation/libs}/react-native.js +0 -0
- /package/{lib/instrumentor → instrumentation}/model/Reference.js +0 -0
- /package/{lib/instrumentor → instrumentation}/parser/Babel.js +0 -0
- /package/lib/{instrumentor/base → core}/DynatraceBridge.js +0 -0
- /package/lib/{instrumentor/base → core}/NullAction.js +0 -0
- /package/lib/{instrumentor/base → core}/NullRootAction.js +0 -0
- /package/lib/{instrumentor/base → core}/NullWebRequestTiming.js +0 -0
- /package/lib/{instrumentor/base → core}/UserPrivacyOptions.js +0 -0
- /package/lib/{instrumentor/base → core}/configuration/ConfigurationBuilder.js +0 -0
- /package/lib/{instrumentor/base → core}/configuration/ConfigurationPreset.js +0 -0
- /package/lib/{instrumentor/base → core}/configuration/IConfiguration.js +0 -0
- /package/lib/{instrumentor/base → core}/interface/IDynatrace.js +0 -0
- /package/lib/{instrumentor/base → core}/interface/IDynatraceAction.js +0 -0
- /package/lib/{instrumentor/base → core}/interface/IDynatraceInternal.js +0 -0
- /package/lib/{instrumentor/base → core}/interface/IDynatraceRootAction.js +0 -0
- /package/lib/{instrumentor/base → core}/interface/IWebRequestTiming.js +0 -0
- /package/lib/{instrumentor/base → core}/interface/NativeDynatraceBridge.js +0 -0
- /package/lib/{instrumentor/base/interface → core/logging}/ILogger.js +0 -0
- /package/lib/{instrumentor/base → core}/model/DataCollectionLevel.js +0 -0
- /package/lib/{instrumentor/base → core}/model/Json.js +0 -0
- /package/lib/{instrumentor/base → core}/model/Platform.js +0 -0
- /package/lib/{instrumentor/base → core}/util/StringUtils.js +0 -0
package/README.md
CHANGED
|
@@ -27,17 +27,18 @@ If you want to start using this plugin and are not a Dynatrace customer yet, hea
|
|
|
27
27
|
* Android Gradle plugin version 7.0+
|
|
28
28
|
* Java 11
|
|
29
29
|
* For iOS users: Minimum iOS 12
|
|
30
|
+
* NodeJS 18+
|
|
30
31
|
|
|
31
32
|
## Agent Versions
|
|
32
33
|
This agent versions are configured in this plugin:
|
|
33
34
|
|
|
34
|
-
* Android Agent: 8.
|
|
35
|
-
* iOS Agent: 8.
|
|
35
|
+
* Android Agent: 8.309.2.1011
|
|
36
|
+
* iOS Agent: 8.309.1.1009
|
|
36
37
|
|
|
37
38
|
## Quick Setup
|
|
38
39
|
|
|
39
40
|
1. [Install plugin](#1-install-the-plugin)
|
|
40
|
-
2. [Register Dynatrace transformer](#2-register-the-dynatrace-transformer)
|
|
41
|
+
2. [Register Dynatrace transformer and reporter](#2-register-the-dynatrace-transformer-and-reporter)
|
|
41
42
|
3. [Setup configuration](#3-setup-dynatraceconfigjs)
|
|
42
43
|
4. [Update Babel Configuration](#4-update-babel-configuration)
|
|
43
44
|
5. [Build and run your app](#4-build-and-run-your-app)
|
|
@@ -106,7 +107,9 @@ This agent versions are configured in this plugin:
|
|
|
106
107
|
- Standalone Project: If you are using React Native standalone and embed it in your native project have a look [here](#configuration-of-standalone-react-native-project).
|
|
107
108
|
- 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](#manually-adding-ios-oneagent-to-a-project).
|
|
108
109
|
|
|
109
|
-
## 2. Register the Dynatrace transformer
|
|
110
|
+
## 2. Register the Dynatrace transformer and reporter
|
|
111
|
+
|
|
112
|
+
The transformer will add modifications to your code during build. The reporter will notify us if you clear the cache of the metro bundler.
|
|
110
113
|
|
|
111
114
|
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.
|
|
112
115
|
|
|
@@ -193,7 +196,7 @@ The required changes for the versions above can be found [here](#react-automatic
|
|
|
193
196
|
|
|
194
197
|
1. Only for Expo: If using expo make sure that your project is containing a "android" and/or "ios" folder. This can be done by using `npx expo prebuild`.
|
|
195
198
|
|
|
196
|
-
2. Execute [`npx instrumentDynatrace`](#npx-instrumentdynatrace) or `react-native instrument-dynatrace` in the root of your React Native project. This will configure both Android and iOS projects with the settings from `dynatrace.config.js`. You can use the same [custom arguments](#customizing-paths-for-configuration) as mentioned above.
|
|
199
|
+
2. Execute [`npx instrumentDynatrace`](#npx-instrumentdynatrace) or `react-native instrument-dynatrace` in the root of your React Native project. This will configure both Android and iOS projects with the settings from `dynatrace.config.js`. You can use the same [custom arguments](#customizing-paths-for-configuration) as mentioned above. Be aware this configuration step will modify your application's *.plist and build.gradle file. This modification enables auto instrumentation on Android and is writing the configuration for both platforms needed for the OneAgent.
|
|
197
200
|
|
|
198
201
|
1. Use `react-native run-android` or `react-native run-ios` to rebuild and run your app. Specify custom paths via [custom arguments.](#customizing-paths-for-configuration).
|
|
199
202
|
|
|
@@ -318,13 +321,13 @@ Dynatrace.withMonitoring(MyFunctionalComponent, "MyFunctionalComponent");
|
|
|
318
321
|
|
|
319
322
|
The String "MyFunctionalComponent" is optional as the name of the component can be retrieved through [different properties](#how-does-dynatrace-determine-the-user-action-name).
|
|
320
323
|
|
|
321
|
-
Combining manual and auto instrumentation
|
|
324
|
+
Combining manual and auto instrumentation should not be a problem. As they're running the same execution, the manual instrumentation will only override the content of auto instrumentation happening through the transformer.
|
|
322
325
|
|
|
323
326
|
### Create custom actions
|
|
324
327
|
|
|
325
328
|
There are two options to create an action. Either using `enterAutoAction` (the previous `enterAction`) or `enterManualAction`:
|
|
326
329
|
|
|
327
|
-
* `enterAutoAction` - Creates an Action which will be automatically handled by the plugin (This is the type of action which is internally used by the plugin when monitoring components and touchables). This means that the plugin decides about the hierachy of this action. If there is no open action, the following action will be a root action. All other actions created by this method, while a root action is open, will be automatically inserted as a child action. Furthermore the plugin will automatically link webrequest (if they are not tagged manually) to the open root action.
|
|
330
|
+
* `enterAutoAction` - Creates an Action which will be automatically handled by the plugin (This is the type of action which is internally used by the plugin when monitoring components and touchables). This means that the plugin decides about the hierachy of this action. If there is no open action, the following action will be a root action. All other actions created by this method, while a root action is open, will be automatically inserted as a child action. Furthermore the plugin will automatically link webrequest (if they are not tagged manually) to the open root action. Be aware that the timeout/wait time for sub actions or web requests cannot be modified on the Android side for React Native Auto actions. The timeout is fixed to a 1000ms.
|
|
328
331
|
|
|
329
332
|
```ts
|
|
330
333
|
import { Dynatrace } from '@dynatrace/react-native-plugin';
|
|
@@ -399,7 +402,7 @@ let url = 'https://www.dynatrace.com';
|
|
|
399
402
|
// You can also use enterAutoAction if desired
|
|
400
403
|
let action = Dynatrace.enterManualAction("Manual Web Request");
|
|
401
404
|
let tag = await action.getRequestTag(url);
|
|
402
|
-
let timing = new DynatraceWebRequestTiming(
|
|
405
|
+
let timing = new DynatraceWebRequestTiming(tag, url);
|
|
403
406
|
|
|
404
407
|
try {
|
|
405
408
|
timing.startWebRequestTiming();
|
|
@@ -1236,7 +1239,7 @@ module.exports = {
|
|
|
1236
1239
|
|
|
1237
1240
|
If you want to register the Dynatrace transformer in your configuration and you already have a transformer in place, change the upstreaming transformer for the Dynatrace transformer.
|
|
1238
1241
|
|
|
1239
|
-
This can be done via a configuration value in the `dynatrace.config.js`. The following example shows how the configuration might look like for the popular `react-native-svg-transformer`. Be aware that the following example is targeting *React Native v0.72.1* or newer.
|
|
1242
|
+
This can be done via a configuration value in the `dynatrace.config.js`. The following example shows how the configuration might look like for the popular `react-native-svg-transformer`. Be aware that the following example is targeting *React Native v0.72.1* or newer. Be aware if you are using a different second transformer, you need to change `react-native-svg-transformer/react-native` accordingly.
|
|
1240
1243
|
|
|
1241
1244
|
#### dynatrace.config.js
|
|
1242
1245
|
|
|
@@ -1244,7 +1247,7 @@ This can be done via a configuration value in the `dynatrace.config.js`. The fol
|
|
|
1244
1247
|
// The `...` only indicates that there are other values as well, but we've omitted them in this example.
|
|
1245
1248
|
module.exports = {
|
|
1246
1249
|
react : {
|
|
1247
|
-
upstreamTransformer: require.resolve('
|
|
1250
|
+
upstreamTransformer: require.resolve('react-native-svg-transformer/react-native'),
|
|
1248
1251
|
...
|
|
1249
1252
|
},
|
|
1250
1253
|
...
|
|
@@ -1460,6 +1463,14 @@ If you are struggling with a problem, submit a support ticket to Dynatrace (supp
|
|
|
1460
1463
|
<br/><br/>
|
|
1461
1464
|
## Changelog
|
|
1462
1465
|
|
|
1466
|
+
2.309.1
|
|
1467
|
+
* Updated Android (8.309.2.1011) & iOS Agent (8.309.1.1009)
|
|
1468
|
+
* Fixed issue with metro when internal require was not resolved correctly
|
|
1469
|
+
|
|
1470
|
+
2.307.1
|
|
1471
|
+
* Updated Android (8.307.1.1005) & iOS Agent (8.307.1.1014)
|
|
1472
|
+
* Now require the use of NodeJS 18+
|
|
1473
|
+
|
|
1463
1474
|
2.305.1
|
|
1464
1475
|
* Updated Android (8.305.1.1005) & iOS Agent (8.305.3.1016)
|
|
1465
1476
|
|
package/android/build.gradle
CHANGED
|
@@ -16,10 +16,12 @@ buildscript {
|
|
|
16
16
|
|
|
17
17
|
dependencies {
|
|
18
18
|
classpath 'com.android.tools.build:gradle:7.3.1'
|
|
19
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10"
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
apply plugin: 'com.android.library'
|
|
24
|
+
apply plugin: 'org.jetbrains.kotlin.android'
|
|
23
25
|
|
|
24
26
|
if (isNewArchitectureEnabled()) {
|
|
25
27
|
apply plugin: 'com.facebook.react'
|
|
@@ -70,7 +72,7 @@ repositories {
|
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
dependencies {
|
|
73
|
-
implementation 'com.dynatrace.agent:agent-android:8.
|
|
75
|
+
implementation 'com.dynatrace.agent:agent-android:8.309.2.1011'
|
|
74
76
|
implementation "com.facebook.react:react-native:${safeExtGet('reactNative', '+')}"
|
|
75
77
|
}
|
|
76
78
|
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
package com.dynatrace.android.agent
|
|
2
|
+
|
|
3
|
+
import android.os.SystemClock
|
|
4
|
+
import com.dynatrace.android.agent.model.AppStartMeasurement
|
|
5
|
+
import com.dynatrace.android.agent.model.AppStartMeasurementType
|
|
6
|
+
import com.facebook.react.bridge.Arguments
|
|
7
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
8
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
9
|
+
import com.facebook.react.bridge.ReactMarker
|
|
10
|
+
import com.facebook.react.bridge.ReactMarkerConstants
|
|
11
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
12
|
+
import java.util.Queue
|
|
13
|
+
import java.util.concurrent.ConcurrentLinkedQueue
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Name of the module
|
|
17
|
+
*/
|
|
18
|
+
private const val APP_START_MODULE = "DynatraceAppStartModule"
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Emitting app start event with this identifier
|
|
22
|
+
*/
|
|
23
|
+
private const val EMIT_APP_START = "dynatraceAppStartMeasurements"
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Queue which contains all our measured app start timestamps
|
|
27
|
+
*/
|
|
28
|
+
private val buffer: Queue<AppStartMeasurement> = ConcurrentLinkedQueue()
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Setup to register all time points which we want to use to report correct app startup time.
|
|
32
|
+
*/
|
|
33
|
+
fun setupAppStartListener() {
|
|
34
|
+
ReactMarker.addListener { name: ReactMarkerConstants?, _: String?, _: Int ->
|
|
35
|
+
when (name) {
|
|
36
|
+
ReactMarkerConstants.RELOAD -> {
|
|
37
|
+
buffer.clear()
|
|
38
|
+
buffer.add(
|
|
39
|
+
AppStartMeasurement(
|
|
40
|
+
AppStartMeasurementType.RELOAD.value,
|
|
41
|
+
SystemClock.uptimeMillis()
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
ReactMarkerConstants.CONTENT_APPEARED -> buffer.add(
|
|
47
|
+
AppStartMeasurement(
|
|
48
|
+
AppStartMeasurementType.CONTENT_APPEARED.value,
|
|
49
|
+
SystemClock.uptimeMillis()
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
ReactMarkerConstants.DOWNLOAD_END -> buffer.add(
|
|
54
|
+
AppStartMeasurement(
|
|
55
|
+
AppStartMeasurementType.DOWNLOAD_END.value,
|
|
56
|
+
SystemClock.uptimeMillis()
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
ReactMarkerConstants.DOWNLOAD_START -> buffer.add(
|
|
61
|
+
AppStartMeasurement(
|
|
62
|
+
AppStartMeasurementType.DOWNLOAD_START.value,
|
|
63
|
+
SystemClock.uptimeMillis()
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
ReactMarkerConstants.RUN_JS_BUNDLE_END -> buffer.add(
|
|
68
|
+
AppStartMeasurement(
|
|
69
|
+
AppStartMeasurementType.RUN_JS_BUNDLE_END.value,
|
|
70
|
+
SystemClock.uptimeMillis()
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
ReactMarkerConstants.RUN_JS_BUNDLE_START -> buffer.add(
|
|
75
|
+
AppStartMeasurement(
|
|
76
|
+
AppStartMeasurementType.RUN_JS_BUNDLE_START.value,
|
|
77
|
+
SystemClock.uptimeMillis()
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
else -> {
|
|
82
|
+
// Not important
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Module which is handling the app start measurements
|
|
90
|
+
*/
|
|
91
|
+
internal class DynatraceAppStartModule(reactContext: ReactApplicationContext?) :
|
|
92
|
+
ReactContextBaseJavaModule(reactContext) {
|
|
93
|
+
init {
|
|
94
|
+
setupInternalListener()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* We setup the listener internally as we
|
|
99
|
+
*/
|
|
100
|
+
private fun setupInternalListener() {
|
|
101
|
+
ReactMarker.addListener { name: ReactMarkerConstants, _: String?, _: Int ->
|
|
102
|
+
if (name == ReactMarkerConstants.CONTENT_APPEARED) {
|
|
103
|
+
// When content appeared we will emit the event with all the information
|
|
104
|
+
flushMeasurements()
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* We flush all events when content appeared is triggered, as it marks the last event
|
|
111
|
+
*/
|
|
112
|
+
private fun flushMeasurements() {
|
|
113
|
+
val params = Arguments.createMap()
|
|
114
|
+
val iterator: Iterator<AppStartMeasurement> = buffer.iterator()
|
|
115
|
+
while (iterator.hasNext()) {
|
|
116
|
+
val entry = iterator.next()
|
|
117
|
+
params.putDouble(entry.name, entry.timestamp.toDouble())
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
reactApplicationContext
|
|
121
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
122
|
+
.emit(EMIT_APP_START, params)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
override fun getName(): String {
|
|
126
|
+
return APP_START_MODULE
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
package com.dynatrace.android.agent
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
4
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
5
|
+
import com.facebook.react.bridge.ReactMethod
|
|
6
|
+
|
|
7
|
+
class DynatraceInternalModule(reactContext: ReactApplicationContext?) :
|
|
8
|
+
ReactContextBaseJavaModule(reactContext) {
|
|
9
|
+
override fun getName(): String {
|
|
10
|
+
return "DynatraceInternalModule"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Helper method which should not be called.
|
|
15
|
+
* @param name Action name
|
|
16
|
+
* @return Action
|
|
17
|
+
*/
|
|
18
|
+
@ReactMethod
|
|
19
|
+
fun enterAction(name: String?): DTXAction {
|
|
20
|
+
return Dynatrace.integrateNewAction(name)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
package com.dynatrace.android.agent
|
|
2
|
+
|
|
3
|
+
import android.location.Location
|
|
4
|
+
import com.dynatrace.android.agent.conf.DataCollectionLevel
|
|
5
|
+
import com.dynatrace.android.agent.conf.DynatraceConfigurationBuilder
|
|
6
|
+
import com.dynatrace.android.agent.conf.UserPrivacyOptions
|
|
7
|
+
import com.dynatrace.android.agent.crash.PlatformType
|
|
8
|
+
import com.facebook.react.bridge.Arguments
|
|
9
|
+
import com.facebook.react.bridge.Promise
|
|
10
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
11
|
+
import com.facebook.react.bridge.ReadableMap
|
|
12
|
+
import org.json.JSONObject
|
|
13
|
+
import java.net.URI
|
|
14
|
+
import java.net.URISyntaxException
|
|
15
|
+
|
|
16
|
+
private const val PLATFORM_ANDROID = "android"
|
|
17
|
+
private const val PLATFORM_IOS = "ios"
|
|
18
|
+
private const val DATA_COLLECTION_OFF = "OFF"
|
|
19
|
+
private const val DATA_COLLECTION_PERFORMANCE = "PERFORMANCE"
|
|
20
|
+
private const val DATA_COLLECTION_USERBEHAVIOR = "USER_BEHAVIOR"
|
|
21
|
+
const val BRIDGE_NAME = "DynatraceBridge"
|
|
22
|
+
|
|
23
|
+
class DynatraceRNBridgeImpl(
|
|
24
|
+
private val reactApplicationContext: ReactApplicationContext,
|
|
25
|
+
private val _internal: DynatraceInternalModule
|
|
26
|
+
) {
|
|
27
|
+
private val webTimings: HashMap<String, WebRequestTiming> = HashMap()
|
|
28
|
+
private val actions: HashMap<String, DTXAction?> = HashMap()
|
|
29
|
+
|
|
30
|
+
private val constants = mapOf(
|
|
31
|
+
"PLATFORM_ANDROID" to PLATFORM_ANDROID,
|
|
32
|
+
"PLATFORM_IOS" to PLATFORM_IOS,
|
|
33
|
+
"DATA_COLLECTION_OFF" to DATA_COLLECTION_OFF,
|
|
34
|
+
"DATA_COLLECTION_PERFORMANCE" to DATA_COLLECTION_PERFORMANCE,
|
|
35
|
+
"DATA_COLLECTION_USERBEHAVIOR" to DATA_COLLECTION_USERBEHAVIOR
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
fun getConstants(): Map<String, Any> {
|
|
39
|
+
return constants;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fun start(configuration: ReadableMap?, promise: Promise) {
|
|
43
|
+
if (configuration == null) {
|
|
44
|
+
promise.resolve(false)
|
|
45
|
+
return
|
|
46
|
+
} else {
|
|
47
|
+
if (configuration.getString("applicationId") == null || configuration.getString("beaconUrl") == null) {
|
|
48
|
+
promise.resolve(false)
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
val builder = DynatraceConfigurationBuilder(
|
|
53
|
+
configuration.getString("applicationId"),
|
|
54
|
+
configuration.getString("beaconUrl")
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
builder.apply {
|
|
58
|
+
withUserOptIn(configuration.getBoolean("userOptIn"))
|
|
59
|
+
withCrashReporting(configuration.getBoolean("reportCrash"))
|
|
60
|
+
// 0 == Debug / 1 == Info
|
|
61
|
+
withDebugLogging(configuration.getInt("logLevel") == 0)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
Dynatrace.startup(reactApplicationContext, builder.buildConfiguration())
|
|
65
|
+
promise.resolve(true)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//
|
|
70
|
+
// Expects a key which is generated in JS. This is to circumvent the async callback system.
|
|
71
|
+
//
|
|
72
|
+
fun enterAction(name: String, key: String, platform: String?) {
|
|
73
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
74
|
+
newAction(name, key)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
fun enterManualAction(name: String, key: String, platform: String?) {
|
|
79
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
80
|
+
actions[key] = Dynatrace.enterAction(name)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fun enterManualActionWithParent(
|
|
85
|
+
name: String,
|
|
86
|
+
key: String,
|
|
87
|
+
parentKey: String,
|
|
88
|
+
platform: String?
|
|
89
|
+
) {
|
|
90
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
91
|
+
val parent = actions[parentKey]
|
|
92
|
+
if (parent != null) {
|
|
93
|
+
actions[key] = Dynatrace.enterAction(name, parent)
|
|
94
|
+
} else {
|
|
95
|
+
enterManualAction(name, key, platform)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
fun leaveAction(key: String, @Suppress("UNUSED_PARAMETER") leave: Boolean, platform: String?) {
|
|
101
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
102
|
+
val action = getAction(key) ?: return
|
|
103
|
+
if (action is DTXAutoAction) {
|
|
104
|
+
action.startTimer()
|
|
105
|
+
} else {
|
|
106
|
+
action.leaveAction()
|
|
107
|
+
}
|
|
108
|
+
actions.remove(key)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fun cancelAction(key: String, platform: String?) {
|
|
113
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
114
|
+
val action = getAction(key) ?: return
|
|
115
|
+
action.cancel()
|
|
116
|
+
actions.remove(key)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
fun endVisit(platform: String?) {
|
|
121
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
122
|
+
Dynatrace.endVisit()
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
fun reportErrorWithoutStacktrace(errorName: String, errorCode: Int, platform: String?) {
|
|
127
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
128
|
+
Dynatrace.internalReportError(errorName, errorCode)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Reports a stacktrace
|
|
134
|
+
* @param errorName Name of the Error - SyntaxError
|
|
135
|
+
* @param errorValue Value of the Error
|
|
136
|
+
* @param reason Reason for the Error
|
|
137
|
+
* @param stacktrace Whole Stacktrace
|
|
138
|
+
* @param platform Platform wise or both
|
|
139
|
+
*/
|
|
140
|
+
fun reportError(
|
|
141
|
+
errorName: String,
|
|
142
|
+
errorValue: String,
|
|
143
|
+
reason: String,
|
|
144
|
+
stacktrace: String,
|
|
145
|
+
platform: String?
|
|
146
|
+
) {
|
|
147
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
148
|
+
Dynatrace.reportError(PlatformType.CUSTOM, errorName, errorValue, reason, stacktrace)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
fun reportCrash(
|
|
153
|
+
errorName: String,
|
|
154
|
+
reason: String,
|
|
155
|
+
stacktrace: String,
|
|
156
|
+
isRealError: Boolean,
|
|
157
|
+
platform: String?
|
|
158
|
+
) {
|
|
159
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
160
|
+
Dynatrace.reportCrash(
|
|
161
|
+
if (isRealError) PlatformType.JAVA_SCRIPT else PlatformType.CUSTOM,
|
|
162
|
+
errorName,
|
|
163
|
+
reason,
|
|
164
|
+
stacktrace
|
|
165
|
+
)
|
|
166
|
+
// Generate new session like iOS to make sure the behavior is the same
|
|
167
|
+
Dynatrace.createNewSession()
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
fun reportErrorInAction(key: String, errorName: String, errorCode: Int, platform: String?) {
|
|
172
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
173
|
+
val action = getAction(key) ?: return
|
|
174
|
+
action.reportError(errorName, errorCode)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
fun reportValue(key: String, valueName: String, value: String, platform: String?) {
|
|
179
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
180
|
+
val action = getAction(key) ?: return
|
|
181
|
+
action.reportValue(valueName, value)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
fun getRequestTag(key: String, @Suppress("UNUSED_PARAMETER") url: String, promise: Promise) {
|
|
186
|
+
val action = getAction(key)
|
|
187
|
+
if (action == null) {
|
|
188
|
+
promise.resolve(Dynatrace.getRequestTag())
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
promise.resolve(action.requestTag)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
fun startWebRequestTiming(requestTag: String?, @Suppress("UNUSED_PARAMETER") url: String) {
|
|
195
|
+
if (requestTag != null) {
|
|
196
|
+
val timing = Dynatrace.getWebRequestTiming(requestTag)
|
|
197
|
+
if (timing != null) {
|
|
198
|
+
webTimings[requestTag] = timing
|
|
199
|
+
timing.startWebRequestTiming()
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
fun stopWebRequestTiming(
|
|
205
|
+
requestTag: String?,
|
|
206
|
+
url: String,
|
|
207
|
+
responseCode: Int,
|
|
208
|
+
responseMessage: String
|
|
209
|
+
) {
|
|
210
|
+
stopWebRequestTimingWithSize(requestTag, url, responseCode, responseMessage, -1, -1)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
fun stopWebRequestTimingWithSize(
|
|
214
|
+
requestTag: String?,
|
|
215
|
+
url: String,
|
|
216
|
+
responseCode: Int,
|
|
217
|
+
responseMessage: String,
|
|
218
|
+
requestSize: Long,
|
|
219
|
+
responseSize: Long
|
|
220
|
+
) {
|
|
221
|
+
if (requestTag != null) {
|
|
222
|
+
val timing = webTimings[requestTag]
|
|
223
|
+
if (timing != null) {
|
|
224
|
+
try {
|
|
225
|
+
timing.stopWebRequestTiming(
|
|
226
|
+
URI(url),
|
|
227
|
+
responseCode,
|
|
228
|
+
responseMessage,
|
|
229
|
+
requestSize,
|
|
230
|
+
responseSize
|
|
231
|
+
)
|
|
232
|
+
webTimings.remove(requestTag)
|
|
233
|
+
} catch (ex: URISyntaxException) {
|
|
234
|
+
// do nothing
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
fun identifyUser(user: String, platform: String?) {
|
|
241
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
242
|
+
Dynatrace.identifyUser(user)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
fun reportEventInAction(actionKey: String, name: String, platform: String?) {
|
|
247
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
248
|
+
val action = getAction(actionKey) ?: return
|
|
249
|
+
action.reportEvent(name)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
fun reportStringValueInAction(
|
|
254
|
+
actionKey: String,
|
|
255
|
+
name: String,
|
|
256
|
+
value: String,
|
|
257
|
+
platform: String?
|
|
258
|
+
) {
|
|
259
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
260
|
+
val action = getAction(actionKey) ?: return
|
|
261
|
+
action.reportValue(name, value)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
fun reportIntValueInAction(actionKey: String, name: String, value: Int, platform: String?) {
|
|
266
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
267
|
+
val action = getAction(actionKey) ?: return
|
|
268
|
+
action.reportValue(name, value)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
fun reportDoubleValueInAction(
|
|
273
|
+
actionKey: String,
|
|
274
|
+
name: String,
|
|
275
|
+
value: Double,
|
|
276
|
+
platform: String?
|
|
277
|
+
) {
|
|
278
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
279
|
+
val action = getAction(actionKey) ?: return
|
|
280
|
+
action.reportValue(name, value)
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
fun sendBizEvent(type: String, attributes: ReadableMap, platform: String?) {
|
|
285
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
286
|
+
Dynatrace.sendBizEvent(type, JSONObject(DynatraceUtils.toHashMap(attributes)))
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
fun forwardEvent(attributes: ReadableMap) {
|
|
291
|
+
HybridBridge.forwardEvent(JSONObject(DynatraceUtils.toHashMap(attributes)))
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
fun startView(name: String) {
|
|
295
|
+
Dynatrace.startView(name)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
fun stopView() {
|
|
299
|
+
Dynatrace.stopView()
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
fun setGPSLocation(lat: Double, lng: Double, platform: String?) {
|
|
303
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
304
|
+
val location = Location("")
|
|
305
|
+
|
|
306
|
+
location.apply {
|
|
307
|
+
latitude = lat
|
|
308
|
+
longitude = lng
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
Dynatrace.setGpsLocation(location)
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
fun flushEvents(platform: String?) {
|
|
316
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
317
|
+
Dynatrace.flushEvents()
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
fun isCrashReportingOptedIn(platform: String?, promise: Promise) {
|
|
322
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
323
|
+
promise.resolve(Dynatrace.isCrashReportingOptedIn())
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
fun setCrashReportingOptedIn(crashReporting: Boolean, platform: String?) {
|
|
328
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
329
|
+
Dynatrace.setCrashReportingOptedIn(crashReporting)
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
fun setDataCollectionLevel(collectionLevel: String, platform: String?) {
|
|
334
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
335
|
+
Dynatrace.setDataCollectionLevel(DataCollectionLevel.valueOf(collectionLevel))
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
fun getDataCollectionLevel(platform: String?, promise: Promise) {
|
|
340
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
341
|
+
val level = Dynatrace.getDataCollectionLevel()
|
|
342
|
+
promise.resolve(level.name)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
fun setBeaconHeaders(headers: ReadableMap?, platform: String?) {
|
|
347
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
348
|
+
if (headers == null) {
|
|
349
|
+
Dynatrace.setBeaconHeaders(null)
|
|
350
|
+
} else {
|
|
351
|
+
val beaconHeaders = mutableMapOf<String, String?>()
|
|
352
|
+
val iterator = headers.keySetIterator()
|
|
353
|
+
while (iterator.hasNextKey()) {
|
|
354
|
+
val currentKey = iterator.nextKey()
|
|
355
|
+
beaconHeaders[currentKey] = headers.getString(currentKey)
|
|
356
|
+
}
|
|
357
|
+
Dynatrace.setBeaconHeaders(beaconHeaders)
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
fun applyUserPrivacyOptions(userPrivacyOptions: ReadableMap, platform: String?) {
|
|
363
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
364
|
+
val optionsBuilder = UserPrivacyOptions.builder()
|
|
365
|
+
optionsBuilder.withCrashReportingOptedIn(userPrivacyOptions.getBoolean("_crashReportingOptedIn"))
|
|
366
|
+
optionsBuilder.withDataCollectionLevel(
|
|
367
|
+
DataCollectionLevel.valueOf(
|
|
368
|
+
userPrivacyOptions.getString(
|
|
369
|
+
"_dataCollectionLevel"
|
|
370
|
+
)!!
|
|
371
|
+
)
|
|
372
|
+
)
|
|
373
|
+
Dynatrace.applyUserPrivacyOptions(
|
|
374
|
+
optionsBuilder
|
|
375
|
+
.build()
|
|
376
|
+
)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
fun getUserPrivacyOptions(platform: String?, promise: Promise) {
|
|
381
|
+
if (shouldWorkOnAndroid(platform)) {
|
|
382
|
+
val options = Dynatrace.getUserPrivacyOptions()
|
|
383
|
+
val privacyMap = Arguments.createMap()
|
|
384
|
+
privacyMap.putString("dataCollectionLevel", options.dataCollectionLevel.name)
|
|
385
|
+
privacyMap.putBoolean("crashReportingOptedIn", options.isCrashReportingOptedIn)
|
|
386
|
+
promise.resolve(privacyMap)
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
@Suppress("UNUSED_PARAMETER")
|
|
391
|
+
fun addListener(eventName: String) {
|
|
392
|
+
// Here because of event emitter
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
@Suppress("UNUSED_PARAMETER")
|
|
396
|
+
fun removeListeners(count: Double) {
|
|
397
|
+
// Here because of event emitter
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
private fun newAction(name: String, key: String) {
|
|
401
|
+
actions[key] = _internal.enterAction(name)
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
private fun getAction(key: String): DTXAction? {
|
|
405
|
+
return actions[key]
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
private fun shouldWorkOnAndroid(platform: String?): Boolean {
|
|
409
|
+
return platform == null || platform == PLATFORM_ANDROID || platform == ""
|
|
410
|
+
}
|
|
411
|
+
}
|