diff --git a/android/build.gradle b/android/build.gradle
index 60e0a72..af476af 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -1,95 +1,74 @@
buildscript {
- // Buildscript is evaluated before everything else so we can't use getExtOrDefault
- def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["ClickstreamReactNative_kotlinVersion"]
+ // Buildscript is evaluated before everything else so we can't use getExtOrDefault
+ def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["ClickstreamReactNative_kotlinVersion"]
- repositories {
- google()
- mavenCentral()
- }
+ repositories {
+ google()
+ mavenCentral()
+ }
- dependencies {
- classpath "com.android.tools.build:gradle:7.2.1"
- // noinspection DifferentKotlinGradleVersion
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
- }
+ dependencies {
+ classpath "com.android.tools.build:gradle:7.2.2"
+ // noinspection DifferentKotlinGradleVersion
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ }
}
def isNewArchitectureEnabled() {
- return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
+ return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
}
apply plugin: "com.android.library"
apply plugin: "kotlin-android"
if (isNewArchitectureEnabled()) {
- apply plugin: "com.facebook.react"
+ apply plugin: "com.facebook.react"
}
def getExtOrDefault(name) {
- return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["ClickstreamReactNative_" + name]
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["ClickstreamReactNative_" + name]
}
def getExtOrIntegerDefault(name) {
- return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["ClickstreamReactNative_" + name]).toInteger()
-}
-
-def supportsNamespace() {
- def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
- def major = parsed[0].toInteger()
- def minor = parsed[1].toInteger()
-
- // Namespace support was added in 7.3.0
- return (major == 7 && minor >= 3) || major >= 8
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["ClickstreamReactNative_" + name]).toInteger()
}
android {
- if (supportsNamespace()) {
- namespace "com.clickstreamreactnative"
-
- sourceSets {
- main {
- manifest.srcFile "src/main/AndroidManifestNew.xml"
- }
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
+ defaultConfig {
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
}
- }
- compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
-
- defaultConfig {
- minSdkVersion getExtOrIntegerDefault("minSdkVersion")
- targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
-
- }
-
- buildTypes {
- release {
- minifyEnabled false
+ buildTypes {
+ release {
+ minifyEnabled false
+ }
}
- }
- lintOptions {
- disable "GradleCompatible"
- }
+ lintOptions {
+ disable "GradleCompatible"
+ }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
}
repositories {
- mavenCentral()
- google()
+ mavenCentral()
+ google()
}
def kotlin_version = getExtOrDefault("kotlinVersion")
dependencies {
- // For < 0.71, this will be from the local maven repo
- // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
- //noinspection GradleDynamicVersion
- implementation "com.facebook.react:react-native:+"
- implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
- implementation "software.aws.solution:clickstream:0.11.1"
+ // For < 0.71, this will be from the local maven repo
+ // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
+ //noinspection GradleDynamicVersion
+ implementation "com.facebook.react:react-native:+"
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+ implementation "software.aws.solution:clickstream:0.12.0"
}
diff --git a/android/gradle.properties b/android/gradle.properties
index 908136f..f44d713 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -1,4 +1,4 @@
ClickstreamReactNative_kotlinVersion=1.7.0
-ClickstreamReactNative_minSdkVersion=21
-ClickstreamReactNative_targetSdkVersion=31
-ClickstreamReactNative_compileSdkVersion=31
+ClickstreamReactNative_minSdkVersion=16
+ClickstreamReactNative_targetSdkVersion=30
+ClickstreamReactNative_compileSdkVersion=30
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index 5e4e43f..7428d3e 100644
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -1,3 +1,2 @@
-
+ package="software.aws.solution.clickstreamreactnative"/>
diff --git a/android/src/main/java/com/clickstreamreactnative/ClickstreamReactNativeModule.kt b/android/src/main/java/com/clickstreamreactnative/ClickstreamReactNativeModule.kt
deleted file mode 100644
index 378d706..0000000
--- a/android/src/main/java/com/clickstreamreactnative/ClickstreamReactNativeModule.kt
+++ /dev/null
@@ -1,148 +0,0 @@
-/**
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
- * with the License. A copy of the License is located at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
- * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
- * and limitations under the License.
- */
-package com.clickstreamreactnative
-
-import com.amazonaws.logging.LogFactory
-import com.amazonaws.logging.Log
-import com.amplifyframework.AmplifyException
-import com.amplifyframework.core.AmplifyConfiguration
-import com.facebook.react.bridge.ReactApplicationContext
-import com.facebook.react.bridge.ReactContextBaseJavaModule
-import com.facebook.react.bridge.ReactMethod
-import com.facebook.react.bridge.Promise
-import com.facebook.react.bridge.ReadableMap
-import com.amplifyframework.core.Amplify
-import org.json.JSONObject
-import software.aws.solution.clickstream.AWSClickstreamPlugin
-import software.aws.solution.clickstream.ClickstreamAnalytics
-import software.aws.solution.clickstream.ClickstreamEvent
-import software.aws.solution.clickstream.ClickstreamItem
-import java.util.concurrent.ExecutorService
-import java.util.concurrent.Executors
-
-
-class ClickstreamReactNativeModule(reactContext: ReactApplicationContext) :
- ReactContextBaseJavaModule(reactContext) {
- private var isInitialized = false
- private val cachedThreadPool: ExecutorService by lazy { Executors.newCachedThreadPool() }
- private val log: Log = LogFactory.getLog(
- ClickstreamReactNativeModule::class.java
- )
-
- override fun getName(): String {
- return NAME
- }
-
- // Example method
- @ReactMethod
- fun multiply(a: Double, b: Double, promise: Promise) {
- promise.resolve(a * b)
- }
-
- @ReactMethod
- fun configure(map: ReadableMap, promise: Promise) {
- if (isInitialized) {
- promise.resolve(false)
- }
-// if (ThreadUtil.notInMainThread()) {
-// log.error("Clickstream SDK initialization failed, please initialize in the main thread")
-// promise.resolve(false)
-// }
- val context = reactApplicationContext
- val initConfig = map.toHashMap()
- val amplifyObject = JSONObject()
- val analyticsObject = JSONObject()
- val pluginsObject = JSONObject()
- val awsClickstreamPluginObject = JSONObject()
- awsClickstreamPluginObject.put("appId", initConfig["appId"])
- awsClickstreamPluginObject.put("endpoint", initConfig["endpoint"])
- pluginsObject.put("awsClickstreamPlugin", awsClickstreamPluginObject)
- analyticsObject.put("plugins", pluginsObject)
- amplifyObject.put("analytics", analyticsObject)
- val configure = AmplifyConfiguration.fromJson(amplifyObject)
- try {
- Amplify.addPlugin(AWSClickstreamPlugin(context))
- Amplify.configure(configure, context)
- } catch (exception: AmplifyException) {
- log.error("Clickstream SDK initialization failed with error: " + exception.message)
- promise.resolve(false)
- }
- val sessionTimeoutDuration = (initConfig["sessionTimeoutDuration"] as Double).toLong()
- val sendEventsInterval = (initConfig["sendEventsInterval"] as Double).toLong()
- ClickstreamAnalytics.getClickStreamConfiguration()
- .withLogEvents(initConfig["isLogEvents"] as Boolean)
- .withTrackScreenViewEvents(initConfig["isTrackScreenViewEvents"] as Boolean)
- .withTrackUserEngagementEvents(initConfig["isTrackUserEngagementEvents"] as Boolean)
- .withTrackAppExceptionEvents(initConfig["isTrackAppExceptionEvents"] as Boolean)
- .withSendEventsInterval(sendEventsInterval)
- .withSessionTimeoutDuration(sessionTimeoutDuration)
- .withCompressEvents(initConfig["isCompressEvents"] as Boolean)
- .withAuthCookie(initConfig["authCookie"] as String)
- promise.resolve(true)
- isInitialized = true
- }
-
- @ReactMethod
- private fun record(map: ReadableMap) {
- cachedThreadPool.execute {
- val event = map.toHashMap()
- val eventName = event["name"] as String
- val eventBuilder = ClickstreamEvent.builder().name(eventName)
- if (event["items"] != null) {
- val items = event["items"] as ArrayList<*>
- if (items.size > 0) {
- val clickstreamItems = arrayOfNulls(items.size)
- for (index in 0 until items.size) {
- val builder = ClickstreamItem.builder()
- for ((key, value) in (items[index] as HashMap<*, *>)) {
- if (value is String) {
- builder.add(key.toString(), value)
- } else if (value is Double) {
- builder.add(key.toString(), value)
- } else if (value is Boolean) {
- builder.add(key.toString(), value)
- } else if (value is Int) {
- builder.add(key.toString(), value)
- } else if (value is Long) {
- builder.add(key.toString(), value)
- }
- }
- clickstreamItems[index] = builder.build()
- }
- eventBuilder.setItems(clickstreamItems)
- }
- }
- if (event["attributes"] != null) {
- val attributes = event["attributes"] as HashMap<*, *>
- for ((key, value) in attributes) {
- if (value is String) {
- eventBuilder.add(key.toString(), value)
- } else if (value is Double) {
- eventBuilder.add(key.toString(), value)
- } else if (value is Boolean) {
- eventBuilder.add(key.toString(), value)
- } else if (value is Int) {
- eventBuilder.add(key.toString(), value)
- } else if (value is Long) {
- eventBuilder.add(key.toString(), value)
- }
- }
- }
- ClickstreamAnalytics.recordEvent(eventBuilder.build())
- }
- }
-
- companion object {
- const val NAME = "ClickstreamReactNative"
- }
-}
diff --git a/android/src/main/java/software/aws/solution/clickstreamreactnative/ClickstreamReactNativeModule.kt b/android/src/main/java/software/aws/solution/clickstreamreactnative/ClickstreamReactNativeModule.kt
new file mode 100644
index 0000000..3699814
--- /dev/null
+++ b/android/src/main/java/software/aws/solution/clickstreamreactnative/ClickstreamReactNativeModule.kt
@@ -0,0 +1,235 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+package software.aws.solution.clickstreamreactnative
+
+import com.amazonaws.logging.Log
+import com.amazonaws.logging.LogFactory
+import com.facebook.react.bridge.Promise
+import com.facebook.react.bridge.ReactApplicationContext
+import com.facebook.react.bridge.ReactContextBaseJavaModule
+import com.facebook.react.bridge.ReactMethod
+import com.facebook.react.bridge.ReadableArray
+import com.facebook.react.bridge.ReadableMap
+import software.aws.solution.clickstream.ClickstreamAnalytics
+import software.aws.solution.clickstream.ClickstreamAttribute
+import software.aws.solution.clickstream.ClickstreamConfiguration
+import software.aws.solution.clickstream.ClickstreamEvent
+import software.aws.solution.clickstream.ClickstreamItem
+import software.aws.solution.clickstream.ClickstreamUserAttribute
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+
+
+class ClickstreamReactNativeModule(reactContext: ReactApplicationContext) :
+ ReactContextBaseJavaModule(reactContext) {
+ private var isInitialized = false
+ private val cachedThreadPool: ExecutorService by lazy { Executors.newCachedThreadPool() }
+ private val log: Log = LogFactory.getLog(
+ ClickstreamReactNativeModule::class.java
+ )
+
+ override fun getName(): String {
+ return NAME
+ }
+
+ @ReactMethod
+ fun init(map: ReadableMap, promise: Promise) {
+ if (isInitialized) {
+ promise.resolve(false)
+ return
+ }
+ val context = reactApplicationContext.applicationContext
+ val initConfig = map.toHashMap()
+ val sessionTimeoutDuration = (initConfig["sessionTimeoutDuration"] as Double).toLong()
+ val sendEventsInterval = (initConfig["sendEventsInterval"] as Double).toLong()
+ val configuration = ClickstreamConfiguration()
+ .withAppId(initConfig["appId"] as String)
+ .withEndpoint(initConfig["endpoint"] as String)
+ .withLogEvents(initConfig["isLogEvents"] as Boolean)
+ .withTrackScreenViewEvents(initConfig["isTrackScreenViewEvents"] as Boolean)
+ .withTrackUserEngagementEvents(initConfig["isTrackUserEngagementEvents"] as Boolean)
+ .withTrackAppExceptionEvents(initConfig["isTrackAppExceptionEvents"] as Boolean)
+ .withSendEventsInterval(sendEventsInterval)
+ .withSessionTimeoutDuration(sessionTimeoutDuration)
+ .withCompressEvents(initConfig["isCompressEvents"] as Boolean)
+ .withAuthCookie(initConfig["authCookie"] as String)
+
+ (initConfig["globalAttributes"] as? HashMap<*, *>)?.takeIf { it.isNotEmpty() }
+ ?.let { attributes ->
+ val globalAttributes = ClickstreamAttribute.builder()
+ for ((key, value) in attributes) {
+ when (value) {
+ is String -> globalAttributes.add(key.toString(), value)
+ is Double -> globalAttributes.add(key.toString(), value)
+ is Boolean -> globalAttributes.add(key.toString(), value)
+ is Int -> globalAttributes.add(key.toString(), value)
+ is Long -> globalAttributes.add(key.toString(), value)
+ }
+ }
+ configuration.withInitialGlobalAttributes(globalAttributes.build())
+ }
+ val latch = CountDownLatch(1);
+ try {
+ reactApplicationContext.runOnUiQueueThread {
+ ClickstreamAnalytics.init(context, configuration)
+ latch.countDown()
+ }
+ latch.await()
+ promise.resolve(true)
+ isInitialized = true
+ } catch (exception: Exception) {
+ promise.resolve(false)
+ log.error("Clickstream SDK initialization failed with error: " + exception.message)
+ }
+ }
+
+ @ReactMethod
+ private fun record(map: ReadableMap) {
+ cachedThreadPool.execute {
+ val event = map.toHashMap()
+ val eventName = event["name"] as String
+ val eventBuilder = ClickstreamEvent.builder().name(eventName)
+ (event["items"] as? List<*>)?.takeIf { it.isNotEmpty() }?.let { items ->
+ val clickstreamItems = arrayOfNulls(items.size)
+ for (index in items.indices) {
+ val builder = ClickstreamItem.builder()
+ for ((key, value) in (items[index] as HashMap<*, *>)) {
+ when (value) {
+ is String -> builder.add(key.toString(), value)
+ is Double -> builder.add(key.toString(), value)
+ is Boolean -> builder.add(key.toString(), value)
+ is Int -> builder.add(key.toString(), value)
+ is Long -> builder.add(key.toString(), value)
+ }
+ }
+ clickstreamItems[index] = builder.build()
+ }
+ eventBuilder.setItems(clickstreamItems)
+ }
+ (event["attributes"] as? Map<*, *>)?.forEach { (key, value) ->
+ when (value) {
+ is String -> eventBuilder.add(key.toString(), value)
+ is Double -> eventBuilder.add(key.toString(), value)
+ is Boolean -> eventBuilder.add(key.toString(), value)
+ is Int -> eventBuilder.add(key.toString(), value)
+ is Long -> eventBuilder.add(key.toString(), value)
+ }
+ }
+ ClickstreamAnalytics.recordEvent(eventBuilder.build())
+ }
+ }
+
+ @ReactMethod
+ private fun setUserId(userId: String?) {
+ ClickstreamAnalytics.setUserId(userId)
+ }
+
+ @ReactMethod
+ private fun setUserAttributes(map: ReadableMap) {
+ val attributes = map.toHashMap()
+ val builder = ClickstreamUserAttribute.Builder()
+ for ((key, value) in attributes) {
+ when (value) {
+ is String -> builder.add(key, value)
+ is Double -> builder.add(key, value)
+ is Boolean -> builder.add(key, value)
+ is Int -> builder.add(key, value)
+ is Long -> builder.add(key, value)
+ }
+ }
+ builder.build().takeIf { it.userAttributes.size() > 0 }?.let { userAttributes ->
+ ClickstreamAnalytics.addUserAttributes(userAttributes)
+ }
+ }
+
+ @ReactMethod
+ private fun setGlobalAttributes(map: ReadableMap) {
+ val attributes = map.toHashMap()
+ val builder = ClickstreamAttribute.Builder()
+ attributes.forEach { (key, value) ->
+ when (value) {
+ is String -> builder.add(key, value)
+ is Double -> builder.add(key, value)
+ is Boolean -> builder.add(key, value)
+ is Int -> builder.add(key, value)
+ is Long -> builder.add(key, value)
+ }
+ }
+ builder.build().takeIf { it.attributes.size() > 0 }?.let { globalAttributes ->
+ ClickstreamAnalytics.addGlobalAttributes(globalAttributes)
+ }
+ }
+
+ @ReactMethod
+ private fun deleteGlobalAttributes(array: ReadableArray) {
+ val stringList = array.toArrayList().filterIsInstance()
+ if (stringList.isNotEmpty()) {
+ ClickstreamAnalytics.deleteGlobalAttributes(*stringList.toTypedArray())
+ }
+ }
+
+ @ReactMethod
+ private fun updateConfigure(map: ReadableMap?) {
+ map?.toHashMap()?.takeIf { it.isNotEmpty() }?.also { arguments ->
+ val configure = ClickstreamAnalytics.getClickStreamConfiguration()
+ arguments["appId"]?.let {
+ configure.withAppId(it as String)
+ }
+ arguments["endpoint"]?.let {
+ configure.withEndpoint(it as String)
+ }
+ arguments["isLogEvents"]?.let {
+ configure.withLogEvents(it as Boolean)
+ }
+ arguments["isTrackScreenViewEvents"]?.let {
+ configure.withTrackScreenViewEvents(it as Boolean)
+ }
+ arguments["isTrackUserEngagementEvents"]?.let {
+ configure.withTrackUserEngagementEvents(it as Boolean)
+ }
+ arguments["isTrackAppExceptionEvents"]?.let {
+ configure.withTrackAppExceptionEvents(it as Boolean)
+ }
+ arguments["isCompressEvents"]?.let {
+ configure.withCompressEvents(it as Boolean)
+ }
+ arguments["authCookie"]?.let {
+ configure.withAuthCookie(it as String)
+ }
+ }
+ }
+
+ @ReactMethod
+ private fun flushEvents() {
+ ClickstreamAnalytics.flushEvents()
+ }
+
+ @ReactMethod
+ private fun disable() {
+ reactApplicationContext.runOnUiQueueThread {
+ ClickstreamAnalytics.disable()
+ }
+ }
+
+ @ReactMethod
+ private fun enable() {
+ reactApplicationContext.runOnUiQueueThread {
+ ClickstreamAnalytics.enable()
+ }
+ }
+
+ companion object {
+ const val NAME = "ClickstreamReactNative"
+ }
+}
diff --git a/android/src/main/java/com/clickstreamreactnative/ClickstreamReactNativePackage.kt b/android/src/main/java/software/aws/solution/clickstreamreactnative/ClickstreamReactNativePackage.kt
similarity index 69%
rename from android/src/main/java/com/clickstreamreactnative/ClickstreamReactNativePackage.kt
rename to android/src/main/java/software/aws/solution/clickstreamreactnative/ClickstreamReactNativePackage.kt
index 54f5330..def3c2e 100644
--- a/android/src/main/java/com/clickstreamreactnative/ClickstreamReactNativePackage.kt
+++ b/android/src/main/java/software/aws/solution/clickstreamreactnative/ClickstreamReactNativePackage.kt
@@ -10,7 +10,7 @@
* OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/
-package com.clickstreamreactnative
+package software.aws.solution.clickstreamreactnative
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
@@ -19,11 +19,11 @@ import com.facebook.react.uimanager.ViewManager
class ClickstreamReactNativePackage : ReactPackage {
- override fun createNativeModules(reactContext: ReactApplicationContext): List {
- return listOf(ClickstreamReactNativeModule(reactContext))
- }
+ override fun createNativeModules(reactContext: ReactApplicationContext): List {
+ return listOf(ClickstreamReactNativeModule(reactContext))
+ }
- override fun createViewManagers(reactContext: ReactApplicationContext): List> {
- return emptyList()
- }
+ override fun createViewManagers(reactContext: ReactApplicationContext): List> {
+ return emptyList()
+ }
}
diff --git a/example/ios/ClickstreamReactNativeExample/Info.plist b/example/ios/ClickstreamReactNativeExample/Info.plist
index 18e41c8..3d0e8b5 100644
--- a/example/ios/ClickstreamReactNativeExample/Info.plist
+++ b/example/ios/ClickstreamReactNativeExample/Info.plist
@@ -26,10 +26,7 @@
NSAppTransportSecurity
-
NSAllowsArbitraryLoads
-
- NSAllowsLocalNetworking
NSLocationWhenInUseUsageDescription
diff --git a/example/ios/Podfile b/example/ios/Podfile
index 0d3bad7..83546bf 100644
--- a/example/ios/Podfile
+++ b/example/ios/Podfile
@@ -5,7 +5,7 @@ require Pod::Executable.execute_command('node', ['-p',
{paths: [process.argv[1]]},
)', __dir__]).strip
-platform :ios, 13.0
+platform :ios, 13.4
prepare_react_native_project!
# If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set.
diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
index ddbe4cf..0b7f1c9 100644
--- a/example/ios/Podfile.lock
+++ b/example/ios/Podfile.lock
@@ -1332,7 +1332,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
Amplify: 516e5da5f256f62841b6bc659e1644bc999d7b6e
boost: d3f49c53809116a5d38da093a8aa78bf551aed09
- clickstream-react-native: 7269f748bcd33ceab502782f18798320764e3ee6
+ clickstream-react-native: ff710a895a3d523452c6850666062b489e9e035f
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953
FBLazyVector: 56e0e498dbb513b96c40bac6284729ba4e62672d
@@ -1396,6 +1396,6 @@ SPEC CHECKSUMS:
SQLite.swift: 4fc2be46c36392e3b87afe6fe7f1801c1daa07ef
Yoga: a716eea57d0d3430219c0a5a233e1e93ee931eb7
-PODFILE CHECKSUM: 133fa8278bdce9cfb4f45b4658fd278b67278ede
+PODFILE CHECKSUM: daadd1904a2d3fd60284a76fa1afe9c56098c16f
COCOAPODS: 1.13.0
diff --git a/example/package.json b/example/package.json
index 9f209c5..4608606 100644
--- a/example/package.json
+++ b/example/package.json
@@ -6,7 +6,7 @@
"android": "react-native run-android",
"ios": "react-native run-ios",
"start": "react-native start",
- "build:android": "cd android && ./gradlew assembleDebug --no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a",
+ "build:android": "cd android && ./gradlew assembleDebug",
"build:ios": "cd ios && xcodebuild -workspace ClickstreamReactNativeExample.xcworkspace -scheme ClickstreamReactNativeExample -configuration Debug -sdk iphonesimulator CC=clang CPLUSPLUS=clang++ LD=clang LDPLUSPLUS=clang++ GCC_OPTIMIZATION_LEVEL=0 GCC_PRECOMPILE_PREFIX_HEADER=YES ASSETCATALOG_COMPILER_OPTIMIZATION=time DEBUG_INFORMATION_FORMAT=dwarf COMPILER_INDEX_STORE_ENABLE=NO"
},
"dependencies": {
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 00997a8..f786371 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -1,70 +1,180 @@
import * as React from 'react';
-import { StyleSheet, View, Text } from 'react-native';
+import {
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ ScrollView,
+ SafeAreaView,
+} from 'react-native';
import { ClickstreamAnalytics } from 'clickstream-react-native';
export default function App() {
- const [result, setResult] = React.useState();
- const [initResult, setInitResult] = React.useState();
-
- React.useEffect(() => {
- ClickstreamAnalytics.multiply(3, 7).then(setResult);
- ClickstreamAnalytics.configure({
- appId: '123',
- endpoint: 'https://example.com/collect',
+ const initSDK = async () => {
+ const res = await ClickstreamAnalytics.init({
+ appId: 'shopping',
+ endpoint:
+ 'http://Clicks-Inges-GMCZD4cV3Xyp-634383170.us-east-1.elb.amazonaws.com/collect',
isLogEvents: true,
- sendEventsInterval: 8000,
- isTrackScreenViewEvents: false,
+ sendEventsInterval: 10000,
+ isTrackScreenViewEvents: true,
isCompressEvents: false,
- sessionTimeoutDuration: 20000,
- }).then(setInitResult);
-
+ sessionTimeoutDuration: 30000,
+ globalAttributes: {
+ channel: 'Samsung',
+ Class: 5,
+ isTrue: true,
+ Score: 24.32,
+ },
+ });
+ console.log('init result is:' + res);
+ };
+ const recordEventWithName = () => {
+ ClickstreamAnalytics.record({
+ name: 'testEventWithName',
+ });
+ };
+ const recordEventWithAttributes = () => {
ClickstreamAnalytics.record({
- name: 'button_click',
+ name: 'testEventWithAttributes',
attributes: {
category: 'shoes',
- currency: 'CNY',
intValue: 13,
- longValue: 99999999139919,
- doubleValue: 11.1234567890121213,
boolValue: true,
value: 279.9,
},
+ });
+ };
+ const recordEventWithItems = () => {
+ ClickstreamAnalytics.record({
+ name: 'product_view',
+ attributes: {
+ category: 'shoes',
+ currency: 'CNY',
+ price: 279.9,
+ },
items: [
{
id: '1',
- name: 'testName1',
- brand: 'Google',
+ name: 'boy shoes',
+ brand: 'Nike',
currency: 'CNY',
- category: 'book',
+ category: 'shoes',
locationId: '1',
- intValue: 13,
- longValue: 99999999139919,
- doubleValue: 11.1234567890121213,
- boolValue: true,
- value: 279.9,
},
],
});
+ };
+ const setUserId = () => {
+ ClickstreamAnalytics.setUserId('123');
+ };
+
+ const setUserIdNull = () => {
+ ClickstreamAnalytics.setUserId(null);
+ };
+
+ const setUserAttributes = () => {
+ ClickstreamAnalytics.setUserAttributes({
+ category: 'shoes',
+ currency: 'CNY',
+ value: 279.9,
+ });
+ ClickstreamAnalytics.setUserAttributes({});
+ };
+ const setGlobalAttributes = () => {
+ ClickstreamAnalytics.setGlobalAttributes({});
+ ClickstreamAnalytics.setGlobalAttributes({
+ channel: 'Samsung',
+ Class: 5,
+ isTrue: true,
+ Score: 24.32,
+ });
+ };
+
+ const deleteGlobalAttributes = () => {
+ ClickstreamAnalytics.deleteGlobalAttributes(['Class', 'isTrue', 'Score']);
+ ClickstreamAnalytics.deleteGlobalAttributes(['']);
+ };
+ const updateConfigure = () => {
+ ClickstreamAnalytics.updateConfigure({
+ appId: 'shopping1',
+ endpoint: 'https://example.com/collect',
+ isLogEvents: true,
+ isCompressEvents: false,
+ isTrackUserEngagementEvents: false,
+ isTrackAppExceptionEvents: false,
+ authCookie: 'test cookie',
+ isTrackScreenViewEvents: false,
+ });
+ };
+
+ const flushEvents = () => {
+ ClickstreamAnalytics.flushEvents();
+ };
+
+ const disable = () => {
+ ClickstreamAnalytics.disable();
+ };
+
+ const enable = () => {
+ ClickstreamAnalytics.enable();
+ };
+
+ React.useEffect(() => {
+ initSDK().then(() => {});
}, []);
return (
-
- Result: {result}
- Init SDK Result: {initResult ? 'success' : 'false'}
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
}
+interface ListItemProps {
+ title: string;
+ onPress: () => void;
+}
+
+const ListItem: React.FC = ({ title, onPress }) => (
+
+ {title}
+
+);
+
const styles = StyleSheet.create({
container: {
flex: 1,
+ marginTop: 20,
+ },
+ listItem: {
+ flexDirection: 'row',
alignItems: 'center',
- justifyContent: 'center',
+ padding: 12,
+ borderBottomWidth: 1,
+ borderBottomColor: '#ddd',
},
- box: {
- width: 60,
- height: 60,
- marginVertical: 20,
+ title: {
+ fontSize: 16,
},
});
diff --git a/ios/Clickstream b/ios/Clickstream
index 534d1f5..3fd0573 160000
--- a/ios/Clickstream
+++ b/ios/Clickstream
@@ -1 +1 @@
-Subproject commit 534d1f51b2733b665e71c6557ee462b4c33196d7
+Subproject commit 3fd05730311702b82eaf5108b60f73a400667901
diff --git a/ios/ClickstreamReactNative.mm b/ios/ClickstreamReactNative.mm
index e1ac2a3..9063935 100644
--- a/ios/ClickstreamReactNative.mm
+++ b/ios/ClickstreamReactNative.mm
@@ -8,18 +8,43 @@
@interface RCT_EXTERN_MODULE(ClickstreamReactNative, NSObject)
-RCT_EXTERN_METHOD(multiply:(float)a:(float)b
+RCT_EXTERN_METHOD(init:(NSDictionary *)arguments
:(RCTPromiseResolveBlock)resolve
:(RCTPromiseRejectBlock)reject)
-RCT_EXTERN_METHOD(configure:(NSDictionary *)arguments
+RCT_EXTERN_METHOD(record:(NSDictionary *)arguments
:(RCTPromiseResolveBlock)resolve
:(RCTPromiseRejectBlock)reject)
-RCT_EXTERN_METHOD(record:(NSDictionary *)arguments
+RCT_EXTERN_METHOD(setUserId:(NSString *)userId
+ :(RCTPromiseResolveBlock)resolve
+ :(RCTPromiseRejectBlock)reject)
+
+RCT_EXTERN_METHOD(setUserAttributes:(NSDictionary *)arguments
+ :(RCTPromiseResolveBlock)resolve
+ :(RCTPromiseRejectBlock)reject)
+
+RCT_EXTERN_METHOD(setGlobalAttributes:(NSDictionary *)arguments
+ :(RCTPromiseResolveBlock)resolve
+ :(RCTPromiseRejectBlock)reject)
+
+RCT_EXTERN_METHOD(deleteGlobalAttributes:(NSArray *)arguments
:(RCTPromiseResolveBlock)resolve
:(RCTPromiseRejectBlock)reject)
+RCT_EXTERN_METHOD(updateConfigure:(NSDictionary *)arguments
+ :(RCTPromiseResolveBlock)resolve
+ :(RCTPromiseRejectBlock)reject)
+
+RCT_EXTERN_METHOD(flushEvents:(RCTPromiseResolveBlock)resolve
+ :(RCTPromiseRejectBlock)reject)
+
+RCT_EXTERN_METHOD(disable:(RCTPromiseResolveBlock)resolve
+ :(RCTPromiseRejectBlock)reject)
+
+RCT_EXTERN_METHOD(enable:(RCTPromiseResolveBlock)resolve
+ :(RCTPromiseRejectBlock)reject)
+
+ (BOOL)requiresMainQueueSetup
{
return NO;
diff --git a/ios/ClickstreamReactNative.swift b/ios/ClickstreamReactNative.swift
index 75d0eaf..6ea2114 100644
--- a/ios/ClickstreamReactNative.swift
+++ b/ios/ClickstreamReactNative.swift
@@ -8,33 +8,35 @@ import Amplify
@objc(ClickstreamReactNative)
class ClickstreamReactNative: NSObject {
- @objc(multiply::::)
- func multiply(a: Float, b: Float, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
- resolve(a * b)
- }
+ var isInitialized = false
- @objc(configure:::)
- func configure(arguments: [String: Any], resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
+ @objc(init:::)
+ func `init`(arguments: [String: Any], resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
+ if isInitialized {
+ resolve(false)
+ return
+ }
do {
- let plugins: [String: JSONValue] = [
- "awsClickstreamPlugin": [
- "appId": JSONValue.string(arguments["appId"] as! String),
- "endpoint": JSONValue.string(arguments["endpoint"] as! String),
- "isCompressEvents": JSONValue.boolean(arguments["isCompressEvents"] as! Bool),
- "autoFlushEventsInterval": JSONValue.number(arguments["sendEventsInterval"] as! Double),
- "isTrackAppExceptionEvents": JSONValue.boolean(arguments["isTrackAppExceptionEvents"] as! Bool)
- ]
- ]
- let analyticsConfiguration = AnalyticsCategoryConfiguration(plugins: plugins)
- let config = AmplifyConfiguration(analytics: analyticsConfiguration)
- try Amplify.add(plugin: AWSClickstreamPlugin())
- try Amplify.configure(config)
- let configure = try ClickstreamAnalytics.getClickstreamConfiguration()
- configure.isLogEvents = arguments["isLogEvents"] as! Bool
- configure.isTrackScreenViewEvents = arguments["isTrackScreenViewEvents"] as! Bool
- configure.isTrackUserEngagementEvents = arguments["isTrackUserEngagementEvents"] as! Bool
- configure.sessionTimeoutDuration = arguments["sessionTimeoutDuration"] as! Int64
- configure.authCookie = arguments["authCookie"] as? String
+ let configuration = ClickstreamConfiguration()
+ .withAppId(arguments["appId"] as! String)
+ .withEndpoint(arguments["endpoint"] as! String)
+ .withLogEvents(arguments["isLogEvents"] as! Bool)
+ .withTrackScreenViewEvents(arguments["isTrackScreenViewEvents"] as! Bool)
+ .withTrackUserEngagementEvents(arguments["isTrackUserEngagementEvents"] as! Bool)
+ .withTrackAppExceptionEvents(arguments["isTrackAppExceptionEvents"] as! Bool)
+ .withSendEventInterval(arguments["sendEventsInterval"] as! Int)
+ .withSessionTimeoutDuration(arguments["sessionTimeoutDuration"] as! Int64)
+ .withCompressEvents(arguments["isCompressEvents"] as! Bool)
+ .withAuthCookie(arguments["authCookie"] as! String)
+ if arguments["globalAttributes"] != nil {
+ let attributes = arguments["globalAttributes"] as! [String: Any]
+ if attributes.count > 0 {
+ let globalAttributes = getClickstreamAttributes(attributes)
+ _ = configuration.withInitialGlobalAttributes(globalAttributes)
+ }
+ }
+ try ClickstreamAnalytics.initSDK(configuration)
+ isInitialized = true
resolve(true)
} catch {
log.error("Fail to initialize ClickstreamAnalytics: \(error)")
@@ -45,12 +47,12 @@ class ClickstreamReactNative: NSObject {
@objc(record:::)
func record(arguments: [String: Any], resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
let eventName = arguments["name"] as! String
- let attributes = arguments["attributes"] as! [String: Any]
- let items = arguments["items"] as! [[String: Any]]
+ let attributes = arguments["attributes"] as? [String: Any] ?? [:]
+ let items = arguments["items"] as? [[String: Any]]
if attributes.count > 0 {
- if items.count > 0 {
+ if items != nil, items!.count > 0 {
var clickstreamItems: [ClickstreamAttribute] = []
- for itemObject in items {
+ for itemObject in items! {
clickstreamItems.append(getClickstreamAttributes(itemObject))
}
ClickstreamAnalytics.recordEvent(eventName, getClickstreamAttributes(attributes), clickstreamItems)
@@ -62,6 +64,82 @@ class ClickstreamReactNative: NSObject {
}
}
+ @objc(setUserId:::)
+ func setUserId(userId: String?, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
+ ClickstreamAnalytics.setUserId(userId)
+ }
+
+ @objc(setUserAttributes:::)
+ func setUserAttributes(arguments: [String: Any], resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
+ let userAttributes = getClickstreamAttributes(arguments)
+ if userAttributes.count > 0 {
+ ClickstreamAnalytics.addUserAttributes(getClickstreamAttributes(arguments))
+ }
+ }
+
+ @objc(setGlobalAttributes:::)
+ func setGlobalAttributes(arguments: [String: Any], resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
+ let globalAttributes = getClickstreamAttributes(arguments)
+ if globalAttributes.count > 0 {
+ ClickstreamAnalytics.addGlobalAttributes(getClickstreamAttributes(arguments))
+ }
+ }
+
+ @objc(deleteGlobalAttributes:::)
+ func deleteGlobalAttributes(arguments: [String], resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
+ for attribute in arguments {
+ ClickstreamAnalytics.deleteGlobalAttributes(attribute)
+ }
+ }
+
+ @objc(updateConfigure:::)
+ func updateConfigure(arguments: [String: Any], resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
+ do {
+ let configuration = try ClickstreamAnalytics.getClickstreamConfiguration()
+ if let appId = arguments["appId"] as? String {
+ configuration.appId = appId
+ }
+ if let endpoint = arguments["endpoint"] as? String {
+ configuration.endpoint = endpoint
+ }
+ if let isLogEvents = arguments["isLogEvents"] as? Bool {
+ configuration.isLogEvents = isLogEvents
+ }
+ if let isTrackScreenViewEvents = arguments["isTrackScreenViewEvents"] as? Bool {
+ configuration.isTrackScreenViewEvents = isTrackScreenViewEvents
+ }
+ if let isTrackUserEngagementEvents = arguments["isTrackUserEngagementEvents"] as? Bool {
+ configuration.isTrackUserEngagementEvents = isTrackUserEngagementEvents
+ }
+ if let isTrackAppExceptionEvents = arguments["isTrackAppExceptionEvents"] as? Bool {
+ configuration.isTrackAppExceptionEvents = isTrackAppExceptionEvents
+ }
+ if let isCompressEvents = arguments["isCompressEvents"] as? Bool {
+ configuration.isCompressEvents = isCompressEvents
+ }
+ if let authCookie = arguments["authCookie"] as? String {
+ configuration.authCookie = authCookie
+ }
+ } catch {
+ log.error("Failed to config ClickstreamAnalytics: \(error)")
+ }
+ }
+
+ @objc(flushEvents::)
+ func flushEvents(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
+ ClickstreamAnalytics.flushEvents()
+ }
+
+ @objc(disable::)
+ func disable(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
+ ClickstreamAnalytics.disable()
+ }
+
+ @objc(enable::)
+ func enable(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
+ ClickstreamAnalytics.enable()
+ }
+
func getClickstreamAttributes(_ attrs: [String: Any]) -> ClickstreamAttribute {
var attributes: ClickstreamAttribute = [:]
for (key, value) in attrs {
diff --git a/package.json b/package.json
index 6787f7c..79fcee4 100644
--- a/package.json
+++ b/package.json
@@ -1,106 +1,106 @@
{
- "name": "clickstream-react-native",
- "version": "0.1.0",
- "description": "ClickstreamAnalytics React Native SDK",
- "main": ".lib/index.ts",
- "module": "./lib-esm/index.js",
- "typings": "./lib-esm/index.d.ts",
- "react-native": ".src/index.ts",
- "source": "src/index",
- "scripts": {
- "example": "yarn workspace clickstream-react-native-example start",
- "test": "npx jest -w 1 --coverage",
- "lint": "npx eslint 'src/*.{js,ts,tsx}'",
- "format": "npx prettier --check 'src/**/*.{js,ts}'",
- "clean-all": "rimraf android/build example/android/build example/android/app/build example/ios/build lib lib-esm",
- "clean-js": "rimraf lib lib-esm",
- "build:cjs": "npx tsc --module commonjs",
- "build:esm": "npx tsc --module esnext --outDir lib-esm",
- "build": "npm run clean-js && npm run build:esm && npm run build:cjs",
- "pack": "npm run build && npm pack"
- },
- "repository": {
- "type": "git",
- "url": "https://github.com/awslabs/clickstream-react-native.git"
- },
- "author": "AWS GCR Solutions Team",
- "license": "Apache-2.0",
- "bugs": {
- "url": "https://github.com/awslabs/clickstream-react-native.git/issues"
- },
- "homepage": "https://awslabs.github.io/clickstream-analytics-on-aws",
- "publishConfig": {
- "access": "public"
- },
- "devDependencies": {
- "@react-native/eslint-config": "0.73.2",
- "@types/jest": "^29.5.5",
- "@types/react": "^18.2.44",
- "eslint": "^8.51.0",
- "eslint-config-prettier": "^9.0.0",
- "eslint-plugin-prettier": "^5.0.1",
- "jest": "^29.7.0",
- "prettier": "^3.0.3",
- "react-native": "0.73.5",
- "typescript": "^4.9.5"
- },
- "peerDependencies": {
- "react-native": "*"
- },
- "workspaces": [
- "example"
- ],
- "packageManager": "yarn@3.6.1",
- "eslintConfig": {
- "root": true,
- "extends": [
- "@react-native",
- "prettier"
- ],
- "rules": {
- "prettier/prettier": [
- "error",
- {
- "quoteProps": "consistent",
- "singleQuote": true,
- "tabWidth": 2,
- "trailingComma": "es5",
- "useTabs": false
- }
- ]
- }
- },
- "eslintIgnore": [
- "node_modules/",
- "lib/",
- "lib-esm/"
- ],
- "prettier": {
- "quoteProps": "consistent",
- "singleQuote": true,
- "tabWidth": 2,
- "trailingComma": "es5",
- "useTabs": false
- },
- "jest": {
- "preset": "react-native",
- "modulePathIgnorePatterns": [
- "/example/node_modules",
- "/lib/",
- "/lib-esm/"
- ]
- },
- "files": [
- "src",
- "lib",
- "lib-esm",
- "android/src",
- "android/build.gradle",
- "android/gradle.properties",
- "ios/Clickstream/Sources",
- "ios/ClickstreamReactNative*",
- "*.podspec",
- "!**/__tests__",
- "!**/.*"
- ]
+ "name": "clickstream-react-native",
+ "version": "0.1.0",
+ "description": "ClickstreamAnalytics React Native SDK",
+ "main": ".lib/index.js",
+ "module": "./lib-esm/index.js",
+ "typings": "./lib-esm/index.d.ts",
+ "react-native": ".src/index.ts",
+ "source": "src/index",
+ "scripts": {
+ "example": "yarn workspace clickstream-react-native-example start",
+ "test": "npx jest -w 1 --coverage",
+ "lint": "npx eslint 'src/*.{js,ts,tsx}'",
+ "format": "npx prettier --check 'src/**/*.{js,ts}'",
+ "clean-all": "rimraf android/build example/android/build example/android/app/build example/ios/build lib lib-esm",
+ "clean-js": "rimraf lib lib-esm",
+ "build:cjs": "npx tsc --module commonjs",
+ "build:esm": "npx tsc --module esnext --outDir lib-esm",
+ "build": "npm run clean-js && npm run build:esm && npm run build:cjs",
+ "pack": "npm run build && npm pack"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/awslabs/clickstream-react-native.git"
+ },
+ "author": "AWS GCR Solutions Team",
+ "license": "Apache-2.0",
+ "bugs": {
+ "url": "https://github.com/awslabs/clickstream-react-native.git/issues"
+ },
+ "homepage": "https://awslabs.github.io/clickstream-analytics-on-aws",
+ "publishConfig": {
+ "access": "public"
+ },
+ "devDependencies": {
+ "@react-native/eslint-config": "0.73.2",
+ "@types/jest": "^29.5.5",
+ "@types/react": "^18.2.44",
+ "eslint": "^8.51.0",
+ "eslint-config-prettier": "^9.0.0",
+ "eslint-plugin-prettier": "^5.0.1",
+ "jest": "^29.7.0",
+ "prettier": "^3.0.3",
+ "react-native": "0.73.5",
+ "typescript": "^4.9.5"
+ },
+ "peerDependencies": {
+ "react-native": "*"
+ },
+ "workspaces": [
+ "example"
+ ],
+ "packageManager": "yarn@3.6.1",
+ "eslintConfig": {
+ "root": true,
+ "extends": [
+ "@react-native",
+ "prettier"
+ ],
+ "rules": {
+ "prettier/prettier": [
+ "error",
+ {
+ "quoteProps": "consistent",
+ "singleQuote": true,
+ "tabWidth": 2,
+ "trailingComma": "es5",
+ "useTabs": false
+ }
+ ]
+ }
+ },
+ "eslintIgnore": [
+ "node_modules/",
+ "lib/",
+ "lib-esm/"
+ ],
+ "prettier": {
+ "quoteProps": "consistent",
+ "singleQuote": true,
+ "tabWidth": 2,
+ "trailingComma": "es5",
+ "useTabs": false
+ },
+ "jest": {
+ "preset": "react-native",
+ "modulePathIgnorePatterns": [
+ "/example/node_modules",
+ "/lib/",
+ "/lib-esm/"
+ ]
+ },
+ "files": [
+ "src",
+ "lib",
+ "lib-esm",
+ "android/src",
+ "android/build.gradle",
+ "android/gradle.properties",
+ "ios/Clickstream/Sources",
+ "ios/ClickstreamReactNative*",
+ "*.podspec",
+ "!**/__tests__",
+ "!**/.*"
+ ]
}
diff --git a/src/ClickstreamAnalytics.ts b/src/ClickstreamAnalytics.ts
index 33c2eb8..0b83a6d 100644
--- a/src/ClickstreamAnalytics.ts
+++ b/src/ClickstreamAnalytics.ts
@@ -12,6 +12,7 @@
*/
import { NativeModules, Platform } from 'react-native';
import type { ClickstreamConfiguration, ClickstreamEvent } from './types';
+import { ClickstreamAttribute, Configuration } from './types';
const LINKING_ERROR =
`The package 'clickstream-react-native' doesn't seem to be linked. Make sure: \n\n` +
@@ -31,13 +32,7 @@ const ClickstreamReactNative = NativeModules.ClickstreamReactNative
);
export class ClickstreamAnalytics {
- public static multiply(a: number, b: number): Promise {
- return ClickstreamReactNative.multiply(a, b);
- }
-
- public static configure(
- configuration: ClickstreamConfiguration
- ): Promise {
+ public static init(configure: ClickstreamConfiguration): Promise {
let initConfiguration: ClickstreamConfiguration = {
appId: '',
endpoint: '',
@@ -50,20 +45,63 @@ export class ClickstreamAnalytics {
sessionTimeoutDuration: 1800000,
authCookie: '',
};
- Object.assign(initConfiguration, configuration);
+ Object.assign(initConfiguration, configure);
if (initConfiguration.appId === '' || initConfiguration.endpoint === '') {
console.log('Please configure your appId and endpoint');
- return new Promise(() => {
- return false;
- });
+ return Promise.resolve(false);
}
- return ClickstreamReactNative.configure(initConfiguration);
+ return ClickstreamReactNative.init(initConfiguration);
}
public static record(event: ClickstreamEvent) {
- if (event.name === null || event.name === '') {
+ if (event.name === undefined || event.name === null || event.name === '') {
console.log('Please set your event name');
+ return;
}
ClickstreamReactNative.record(event);
}
+
+ public static setUserId(userId: string | null) {
+ ClickstreamReactNative.setUserId(userId);
+ }
+
+ public static setUserAttributes(userAttributes: ClickstreamAttribute) {
+ if (this.isNotEmpty(userAttributes)) {
+ ClickstreamReactNative.setUserAttributes(userAttributes);
+ }
+ }
+
+ public static setGlobalAttributes(globalAttributes: ClickstreamAttribute) {
+ if (this.isNotEmpty(globalAttributes)) {
+ ClickstreamReactNative.setGlobalAttributes(globalAttributes);
+ }
+ }
+
+ public static deleteGlobalAttributes(globalAttributes: string[]) {
+ if (globalAttributes.length > 0) {
+ ClickstreamReactNative.deleteGlobalAttributes(globalAttributes);
+ }
+ }
+
+ public static updateConfigure(configure: Configuration) {
+ if (this.isNotEmpty(configure)) {
+ ClickstreamReactNative.updateConfigure(configure);
+ }
+ }
+
+ public static flushEvents() {
+ ClickstreamReactNative.flushEvents();
+ }
+
+ public static disable() {
+ ClickstreamReactNative.disable();
+ }
+
+ public static enable() {
+ ClickstreamReactNative.enable();
+ }
+
+ static isNotEmpty(obj: any): boolean {
+ return obj !== undefined && obj !== null && Object.keys(obj).length > 0;
+ }
}
diff --git a/src/__tests__/ClickstreamAnalytics.test.ts b/src/__tests__/ClickstreamAnalytics.test.ts
new file mode 100644
index 0000000..d23b3c9
--- /dev/null
+++ b/src/__tests__/ClickstreamAnalytics.test.ts
@@ -0,0 +1,275 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ * with the License. A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
+ * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+import { ClickstreamAnalytics } from '../index';
+import { NativeModules } from 'react-native';
+
+jest.mock('react-native', () => {
+ const actualNativeModules = jest.requireActual('react-native').NativeModules;
+ return {
+ NativeModules: {
+ ...actualNativeModules,
+ ClickstreamReactNative: {
+ init: jest.fn().mockImplementation(() => {
+ return Promise.resolve(true);
+ }),
+ record: jest.fn(),
+ setUserId: jest.fn(),
+ setUserAttributes: jest.fn(),
+ setGlobalAttributes: jest.fn(),
+ deleteGlobalAttributes: jest.fn(),
+ updateConfigure: jest.fn(),
+ flushEvents: jest.fn(),
+ disable: jest.fn(),
+ enable: jest.fn(),
+ },
+ },
+ Platform: {
+ ...actualNativeModules.Platform,
+ select: jest.fn((obj) => obj.ios || obj.default),
+ },
+ };
+});
+describe('ClickstreamAnalytics test', () => {
+ test('test init SDK with default configuration', async () => {
+ const res = await ClickstreamAnalytics.init({
+ appId: 'testAppId',
+ endpoint: 'https://example.com/collect',
+ });
+ expect(res).toBeTruthy();
+ expect(NativeModules.ClickstreamReactNative.init).toHaveBeenCalledWith(
+ expect.objectContaining({
+ appId: 'testAppId',
+ endpoint: 'https://example.com/collect',
+ sendEventsInterval: 10000,
+ isTrackScreenViewEvents: true,
+ isTrackUserEngagementEvents: true,
+ isTrackAppExceptionEvents: false,
+ isLogEvents: false,
+ isCompressEvents: true,
+ sessionTimeoutDuration: 1800000,
+ authCookie: '',
+ })
+ );
+ });
+
+ test('test init SDK with custom configuration', async () => {
+ await ClickstreamAnalytics.init({
+ appId: 'testAppId',
+ endpoint: 'https://example.com/collect',
+ isLogEvents: true,
+ sendEventsInterval: 10000,
+ isTrackScreenViewEvents: true,
+ isCompressEvents: false,
+ sessionTimeoutDuration: 30000,
+ });
+ expect(NativeModules.ClickstreamReactNative.init).toHaveBeenCalledWith(
+ expect.objectContaining({
+ appId: 'testAppId',
+ endpoint: 'https://example.com/collect',
+ isLogEvents: true,
+ sendEventsInterval: 10000,
+ isTrackScreenViewEvents: true,
+ isCompressEvents: false,
+ sessionTimeoutDuration: 30000,
+ isTrackUserEngagementEvents: true,
+ isTrackAppExceptionEvents: false,
+ authCookie: '',
+ })
+ );
+ });
+
+ test('test init SDK with empty appId', async () => {
+ const res = await ClickstreamAnalytics.init({
+ appId: '',
+ endpoint: 'https://example.com/collect',
+ });
+ expect(res).toBeFalsy();
+ expect(NativeModules.ClickstreamReactNative.init).not.toHaveBeenCalled();
+ });
+
+ test('test init SDK with empty endpoint', async () => {
+ const res = await ClickstreamAnalytics.init({
+ appId: 'testAppId',
+ endpoint: '',
+ });
+ expect(res).toBeFalsy();
+ expect(NativeModules.ClickstreamReactNative.init).not.toHaveBeenCalled();
+ });
+
+ test('test record event with valid event name', () => {
+ ClickstreamAnalytics.record({
+ name: 'product_view',
+ attributes: {
+ category: 'shoes',
+ currency: 'CNY',
+ price: 279.9,
+ },
+ });
+ expect(NativeModules.ClickstreamReactNative.record).toHaveBeenCalledWith(
+ expect.objectContaining({
+ name: 'product_view',
+ attributes: {
+ category: 'shoes',
+ currency: 'CNY',
+ price: 279.9,
+ },
+ })
+ );
+ });
+
+ test('test record event with empty event name', () => {
+ ClickstreamAnalytics.record({
+ name: '',
+ });
+ expect(NativeModules.ClickstreamReactNative.record).not.toHaveBeenCalled();
+ });
+
+ test('test set userId', () => {
+ ClickstreamAnalytics.setUserId('123');
+ expect(NativeModules.ClickstreamReactNative.setUserId).toHaveBeenCalledWith(
+ '123'
+ );
+ });
+
+ test('test set userId null', () => {
+ ClickstreamAnalytics.setUserId(null);
+ expect(NativeModules.ClickstreamReactNative.setUserId).toHaveBeenCalledWith(
+ null
+ );
+ });
+
+ test('test set user attributes', () => {
+ ClickstreamAnalytics.setUserAttributes({
+ user_age: 21,
+ user_name: 'carl',
+ });
+ expect(
+ NativeModules.ClickstreamReactNative.setUserAttributes
+ ).toHaveBeenCalledWith(
+ expect.objectContaining({
+ user_age: 21,
+ user_name: 'carl',
+ })
+ );
+ });
+ test('test set empty user attribute', () => {
+ ClickstreamAnalytics.setUserAttributes({});
+ expect(
+ NativeModules.ClickstreamReactNative.setUserAttributes
+ ).not.toHaveBeenCalled();
+ });
+
+ test('test set global attributes', () => {
+ ClickstreamAnalytics.setGlobalAttributes({
+ channel: 'Samsung',
+ Class: 5,
+ isTrue: true,
+ Score: 24.32,
+ });
+ expect(
+ NativeModules.ClickstreamReactNative.setGlobalAttributes
+ ).toHaveBeenCalledWith(
+ expect.objectContaining({
+ channel: 'Samsung',
+ Class: 5,
+ isTrue: true,
+ Score: 24.32,
+ })
+ );
+ });
+
+ test('test set empty global attribute', () => {
+ ClickstreamAnalytics.setGlobalAttributes({});
+ expect(
+ NativeModules.ClickstreamReactNative.setGlobalAttributes
+ ).not.toHaveBeenCalled();
+ });
+
+ test('test delete global attributes', () => {
+ ClickstreamAnalytics.deleteGlobalAttributes(['Class', 'isTrue', 'Score']);
+ expect(
+ NativeModules.ClickstreamReactNative.deleteGlobalAttributes
+ ).toHaveBeenCalledWith(['Class', 'isTrue', 'Score']);
+ });
+
+ test('test delete empty global attribute', () => {
+ ClickstreamAnalytics.deleteGlobalAttributes([]);
+ expect(
+ NativeModules.ClickstreamReactNative.deleteGlobalAttributes
+ ).not.toHaveBeenCalled();
+ });
+
+ test('test update configure', () => {
+ ClickstreamAnalytics.updateConfigure({
+ appId: 'shopping',
+ endpoint: 'https://example.com/collect',
+ isLogEvents: true,
+ isCompressEvents: false,
+ isTrackUserEngagementEvents: false,
+ isTrackAppExceptionEvents: false,
+ authCookie: 'test cookie',
+ isTrackScreenViewEvents: false,
+ });
+ expect(
+ NativeModules.ClickstreamReactNative.updateConfigure
+ ).toHaveBeenCalledWith(
+ expect.objectContaining({
+ appId: 'shopping',
+ endpoint: 'https://example.com/collect',
+ isLogEvents: true,
+ isCompressEvents: false,
+ isTrackUserEngagementEvents: false,
+ isTrackAppExceptionEvents: false,
+ authCookie: 'test cookie',
+ isTrackScreenViewEvents: false,
+ })
+ );
+ });
+
+ test('test update empty configure', () => {
+ ClickstreamAnalytics.updateConfigure({});
+ expect(
+ NativeModules.ClickstreamReactNative.updateConfigure
+ ).not.toHaveBeenCalled();
+ });
+
+ test('test flush events', () => {
+ ClickstreamAnalytics.flushEvents();
+ expect(NativeModules.ClickstreamReactNative.flushEvents).toHaveBeenCalled();
+ });
+
+ test('test enable SDK', () => {
+ ClickstreamAnalytics.enable();
+ expect(NativeModules.ClickstreamReactNative.enable).toHaveBeenCalled();
+ });
+
+ test('test disable SDK', () => {
+ ClickstreamAnalytics.disable();
+ expect(NativeModules.ClickstreamReactNative.disable).toHaveBeenCalled();
+ });
+
+ test('test isNotEmpty', async () => {
+ expect(
+ ClickstreamAnalytics.isNotEmpty({
+ testKey: '123',
+ })
+ ).toBe(true);
+ expect(ClickstreamAnalytics.isNotEmpty(undefined)).toBe(false);
+ expect(ClickstreamAnalytics.isNotEmpty(null)).toBe(false);
+ expect(ClickstreamAnalytics.isNotEmpty({})).toBe(false);
+ });
+
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+});
diff --git a/src/__tests__/index.test.ts b/src/__tests__/ModuleNotLinked.test.ts
similarity index 53%
rename from src/__tests__/index.test.ts
rename to src/__tests__/ModuleNotLinked.test.ts
index 8ee965a..bdfdf12 100644
--- a/src/__tests__/index.test.ts
+++ b/src/__tests__/ModuleNotLinked.test.ts
@@ -10,8 +10,20 @@
* OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/
-describe('ClickstreamAnalytics test', () => {
- test('test add', () => {
- expect(3 * 7).toBe(21);
+import { ClickstreamAnalytics } from '../index';
+
+describe('ModuleNotLinked test', () => {
+ test('test init SDK when native module unlinked', async () => {
+ try {
+ await ClickstreamAnalytics.init({
+ appId: 'testAppId',
+ endpoint: 'https://example.com/collect',
+ });
+ fail('test failed, should throw linking error');
+ } catch (error) {
+ expect((error as any).message).toContain(
+ "The package 'clickstream-react-native' doesn't seem to be linked"
+ );
+ }
});
});
diff --git a/src/types/Analytics.ts b/src/types/Analytics.ts
index 24a46d1..80eaac4 100644
--- a/src/types/Analytics.ts
+++ b/src/types/Analytics.ts
@@ -12,13 +12,16 @@
*/
export interface ClickstreamConfiguration extends Configuration {
- readonly appId: string;
- readonly endpoint: string;
+ appId: string;
+ endpoint: string;
readonly sendEventsInterval?: number;
readonly sessionTimeoutDuration?: number;
+ readonly globalAttributes?: ClickstreamAttribute;
}
export interface Configuration {
+ appId?: string;
+ endpoint?: string;
isLogEvents?: boolean;
isCompressEvents?: boolean;
authCookie?: string;
@@ -28,7 +31,7 @@ export interface Configuration {
}
export interface ClickstreamAttribute {
- [key: string]: string | number | boolean | null;
+ [key: string]: string | number | boolean;
}
export interface Item {
@@ -47,7 +50,7 @@ export interface Item {
category4?: string;
category5?: string;
- [key: string]: string | number | boolean | null | undefined;
+ [key: string]: string | number | boolean | undefined;
}
export interface ClickstreamEvent {