@attentive-mobile/attentive-react-native-sdk 0.1.0
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/LICENSE +20 -0
- package/README.md +140 -0
- package/android/build.gradle +92 -0
- package/android/github.properties +2 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/com/attentivereactnativesdk/AttentiveReactNativeSdkModule.java +195 -0
- package/android/src/main/java/com/attentivereactnativesdk/AttentiveReactNativeSdkPackage.java +28 -0
- package/attentive-react-native-sdk.podspec +36 -0
- package/ios/AttentiveReactNativeSdk.h +18 -0
- package/ios/AttentiveReactNativeSdk.mm +85 -0
- package/ios/AttentiveReactNativeSdk.xcodeproj/project.pbxproj +276 -0
- package/ios/AttentiveReactNativeSdk.xcodeproj/project.xcworkspace/contents.xcworkspacedata +4 -0
- package/ios/AttentiveReactNativeSdk.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/ios/AttentiveReactNativeSdk.xcodeproj/project.xcworkspace/xcuserdata/wdavis.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/ios/AttentiveReactNativeSdk.xcodeproj/xcuserdata/wdavis.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
- package/ios/attentive-sdk-umbrella.h +23 -0
- package/lib/commonjs/eventTypes.js +2 -0
- package/lib/commonjs/eventTypes.js.map +1 -0
- package/lib/commonjs/index.js +50 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/module/eventTypes.js +2 -0
- package/lib/module/eventTypes.js.map +1 -0
- package/lib/module/index.js +42 -0
- package/lib/module/index.js.map +1 -0
- package/lib/typescript/eventTypes.d.ts +36 -0
- package/lib/typescript/eventTypes.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +30 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/package.json +162 -0
- package/src/eventTypes.tsx +42 -0
- package/src/index.tsx +77 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Attentive Mobile
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# attentive-react-native-sdk
|
|
2
|
+
|
|
3
|
+
# Attentive React Native SDK
|
|
4
|
+
The Attentive React Native SDK provides the functionality to render Attentive creative units and collect Attentive events in React Native mobile applications.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
TODO
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
See the [Example Project](https://github.com/attentive-mobile/attentive-react-native-sdk/blob/main/example)
|
|
12
|
+
for a sample of how the Attentive React Native SDK is used.
|
|
13
|
+
|
|
14
|
+
__*** NOTE: Please refrain from using any private or undocumented classes or methods as they may change between releases. ***__
|
|
15
|
+
|
|
16
|
+
### Import the SDK
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { Attentive, <other types you need here> } from 'attentive-react-native-sdk';
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Create the AttentiveConfig
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
// Create an AttentiveConfiguration with your attentive domain, in production mode
|
|
26
|
+
const config : AttentiveConfiguration = {
|
|
27
|
+
attentiveDomain: 'YOUR_ATTENTIVE_DOMAIN',
|
|
28
|
+
mode: Mode.Production,
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
```typescript
|
|
32
|
+
// Alternatively, use "debug" mode. When in debug mode, the Creative will not be shown, but instead a popup will show with debug information about your creative and any reason the Creative wouldn't show.
|
|
33
|
+
const config : AttentiveConfiguration = {
|
|
34
|
+
attentiveDomain: 'YOUR_ATTENTIVE_DOMAIN',
|
|
35
|
+
mode: Mode.Debug,
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Initialize the SDK
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// 'initialize' should be called as soon as possible after the app starts (see the example app for an example of initializing the SDK in the App element)
|
|
43
|
+
// Note: 'initialize' should only be called once per app session - if you call it multiple times it will throw an exception
|
|
44
|
+
Attentive.initialize(config);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
### Identify the current user
|
|
49
|
+
```typescript
|
|
50
|
+
// Before loading the creative or sending events, if you have any user identifiers, they will need to be registered. Each identifier is optional. It is okay to skip this step if you have no identifiers about the user yet.
|
|
51
|
+
const identifiers : UserIdentifiers = {
|
|
52
|
+
'phone': '+15556667777',
|
|
53
|
+
'email': 'some_email@gmailfake.com',
|
|
54
|
+
'klaviyoId': 'userKlaviyoId',
|
|
55
|
+
'shopifyId': 'userShopifyId',
|
|
56
|
+
'clientUserId': 'userClientUserId',
|
|
57
|
+
'customIdentifiers': { 'customIdKey': 'customIdValue' }
|
|
58
|
+
};
|
|
59
|
+
Attentive.identify(identifiers);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The more identifiers that are passed to `identify`, the better the SDK will function. Here is the list of possible identifiers:
|
|
63
|
+
| Identifier Name | Type | Description |
|
|
64
|
+
| ------------------ | --------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
65
|
+
| Client User ID | String | Your unique identifier for the user. This should be consistent across the user's lifetime. For example, a database id. |
|
|
66
|
+
| Phone | String | The users's phone number in E.164 format |
|
|
67
|
+
| Email | String | The users's email |
|
|
68
|
+
| Shopify ID | String | The users's Shopify ID |
|
|
69
|
+
| Klaviyo ID | String | The users's Klaviyo ID |
|
|
70
|
+
| Custom Identifiers | Map<String,String> | Key-value pairs of custom identifier names and values. The values should be unique to this user. |
|
|
71
|
+
|
|
72
|
+
### Load the Creative
|
|
73
|
+
#### 1. Create the Creative
|
|
74
|
+
```typescript
|
|
75
|
+
// Trigger the Creative. This will show the Creative as a pop-up over the rest of the app.
|
|
76
|
+
Attentive.triggerCreative();
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
#### 3. Destroy the Creative
|
|
80
|
+
|
|
81
|
+
TODO
|
|
82
|
+
|
|
83
|
+
__*** NOTE: You must call the destroy method when the creative is no longer in use to properly clean up the WebView and it's resources ***__
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
### Record user events
|
|
87
|
+
|
|
88
|
+
The SDK currently supports `PurchaseEvent`, `AddToCartEvent`, `ProductViewEvent`, and `CustomEvent`.
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// Construct one or more "Item"s, which represents the product(s) purchased
|
|
92
|
+
const items : Item[] = [
|
|
93
|
+
{
|
|
94
|
+
productId: '555',
|
|
95
|
+
productVariantId: '777',
|
|
96
|
+
price: {
|
|
97
|
+
price: '14.99',
|
|
98
|
+
currency: 'USD',
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
// Construct an "Order", which represents the order for the purchase
|
|
104
|
+
const order : Order = {
|
|
105
|
+
orderId: '88888'
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// (Optional) Construct a "Cart", which represents the cart this Purchase was made from
|
|
109
|
+
const cart : Cart = {
|
|
110
|
+
cartId: '555555',
|
|
111
|
+
cartCoupon: 'SOME-DISCOUNT'
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Construct a PurchaseEvent, which ties together the preceding objects
|
|
115
|
+
const purchaseEvent : PurchaseEvent = {
|
|
116
|
+
items: items,
|
|
117
|
+
order: order,
|
|
118
|
+
cart: cart
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Record the PurchaseEvent
|
|
122
|
+
Attentive.recordPurchaseEvent(purchaseEvent);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The process is similar for the other events. See [eventTypes.tsx](https://github.com/attentive-mobile/attentive-react-native-sdk/blob/main/src/eventTypes.tsx) for all events.
|
|
126
|
+
|
|
127
|
+
### Update the current user when new identifiers are available
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
// If new identifiers are available for the user, register them
|
|
131
|
+
Attentive.identify({email: 'theusersemail@gmail.com'});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
Attentive.identify({email: 'theusersemail@gmail.com'});
|
|
136
|
+
Attentive.identify({phone: '+15556667777'};)
|
|
137
|
+
// The SDK will have these two identifiers:
|
|
138
|
+
// email: 'theusersemail@gmail.com'
|
|
139
|
+
// phone: '+15556667777'
|
|
140
|
+
```
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
repositories {
|
|
3
|
+
google()
|
|
4
|
+
mavenCentral()
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
dependencies {
|
|
8
|
+
classpath "com.android.tools.build:gradle:7.2.1"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
def isNewArchitectureEnabled() {
|
|
13
|
+
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
apply plugin: "com.android.library"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') }
|
|
20
|
+
|
|
21
|
+
if (isNewArchitectureEnabled()) {
|
|
22
|
+
apply plugin: "com.facebook.react"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
def getExtOrDefault(name) {
|
|
26
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["AttentiveReactNativeSdk_" + name]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
def getExtOrIntegerDefault(name) {
|
|
30
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["AttentiveReactNativeSdk_" + name]).toInteger()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
android {
|
|
34
|
+
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
35
|
+
|
|
36
|
+
defaultConfig {
|
|
37
|
+
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
38
|
+
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
39
|
+
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
40
|
+
}
|
|
41
|
+
buildTypes {
|
|
42
|
+
release {
|
|
43
|
+
minifyEnabled false
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
lintOptions {
|
|
48
|
+
disable "GradleCompatible"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
compileOptions {
|
|
52
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
53
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
Properties ghProperties = new Properties()
|
|
59
|
+
File ghPropertiesFile = file("github.properties")
|
|
60
|
+
if (!ghPropertiesFile.exists()) {
|
|
61
|
+
throw new GradleException('Could not find github.properties file.')
|
|
62
|
+
}
|
|
63
|
+
ghProperties.load(ghPropertiesFile.newDataInputStream())
|
|
64
|
+
|
|
65
|
+
repositories {
|
|
66
|
+
mavenCentral()
|
|
67
|
+
google()
|
|
68
|
+
maven {
|
|
69
|
+
url = uri("https://maven.pkg.github.com/attentive-mobile/attentive-android-sdk")
|
|
70
|
+
credentials {
|
|
71
|
+
username = ghProperties.getProperty('gpr.user')
|
|
72
|
+
password = ghProperties.getProperty('gpr.key')
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
dependencies {
|
|
79
|
+
// For < 0.71, this will be from the local maven repo
|
|
80
|
+
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
|
|
81
|
+
//noinspection GradleDynamicVersion
|
|
82
|
+
implementation "com.facebook.react:react-native:+"
|
|
83
|
+
implementation 'com.attentive:attentive-android-sdk:0.4.0'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (isNewArchitectureEnabled()) {
|
|
87
|
+
react {
|
|
88
|
+
jsRootDir = file("../src/")
|
|
89
|
+
libraryName = "AttentiveReactNativeSdk"
|
|
90
|
+
codegenJavaPackageName = "com.attentivereactnativesdk"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
package com.attentivereactnativesdk;
|
|
2
|
+
|
|
3
|
+
import android.app.Activity;
|
|
4
|
+
import android.util.Log;
|
|
5
|
+
import android.view.ViewGroup;
|
|
6
|
+
import androidx.annotation.NonNull;
|
|
7
|
+
|
|
8
|
+
import com.attentive.androidsdk.AttentiveConfig;
|
|
9
|
+
import com.attentive.androidsdk.AttentiveEventTracker;
|
|
10
|
+
import com.attentive.androidsdk.UserIdentifiers;
|
|
11
|
+
import com.attentive.androidsdk.creatives.Creative;
|
|
12
|
+
import com.attentive.androidsdk.events.AddToCartEvent;
|
|
13
|
+
import com.attentive.androidsdk.events.CustomEvent;
|
|
14
|
+
import com.attentive.androidsdk.events.Item;
|
|
15
|
+
import com.attentive.androidsdk.events.Order;
|
|
16
|
+
import com.attentive.androidsdk.events.Price;
|
|
17
|
+
import com.attentive.androidsdk.events.ProductViewEvent;
|
|
18
|
+
import com.attentive.androidsdk.events.PurchaseEvent;
|
|
19
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
20
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
21
|
+
import com.facebook.react.bridge.ReactMethod;
|
|
22
|
+
import com.facebook.react.bridge.ReadableArray;
|
|
23
|
+
import com.facebook.react.bridge.ReadableMap;
|
|
24
|
+
import com.facebook.react.bridge.UiThreadUtil;
|
|
25
|
+
import com.facebook.react.module.annotations.ReactModule;
|
|
26
|
+
import java.math.BigDecimal;
|
|
27
|
+
import java.security.InvalidParameterException;
|
|
28
|
+
import java.util.ArrayList;
|
|
29
|
+
import java.util.Arrays;
|
|
30
|
+
import java.util.Currency;
|
|
31
|
+
import java.util.HashMap;
|
|
32
|
+
import java.util.List;
|
|
33
|
+
import java.util.Locale;
|
|
34
|
+
import java.util.Map;
|
|
35
|
+
|
|
36
|
+
@ReactModule(name = AttentiveReactNativeSdkModule.NAME)
|
|
37
|
+
public class AttentiveReactNativeSdkModule extends ReactContextBaseJavaModule {
|
|
38
|
+
public static final String NAME = "AttentiveReactNativeSdk";
|
|
39
|
+
private static final String TAG = NAME;
|
|
40
|
+
|
|
41
|
+
private AttentiveConfig attentiveConfig;
|
|
42
|
+
|
|
43
|
+
public AttentiveReactNativeSdkModule(ReactApplicationContext reactContext) {
|
|
44
|
+
super(reactContext);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@Override
|
|
48
|
+
@NonNull
|
|
49
|
+
public String getName() {
|
|
50
|
+
return NAME;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@ReactMethod
|
|
54
|
+
public void initialize(ReadableMap config) {
|
|
55
|
+
final String rawMode = config.getString("mode");
|
|
56
|
+
if (rawMode == null) {
|
|
57
|
+
throw new IllegalArgumentException("The 'mode' parameter cannot be null.");
|
|
58
|
+
}
|
|
59
|
+
final String domain = config.getString("attentiveDomain");
|
|
60
|
+
attentiveConfig = new AttentiveConfig(domain, AttentiveConfig.Mode.valueOf(rawMode.toUpperCase(Locale.ROOT)), this.getReactApplicationContext());
|
|
61
|
+
AttentiveEventTracker.getInstance().initialize(attentiveConfig);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@ReactMethod
|
|
65
|
+
public void triggerCreative() {
|
|
66
|
+
Log.i(TAG, "Native Attentive module was called to trigger the creative.");
|
|
67
|
+
try {
|
|
68
|
+
Activity currentActivity = getReactApplicationContext().getCurrentActivity();
|
|
69
|
+
if (currentActivity != null) {
|
|
70
|
+
ViewGroup rootView =
|
|
71
|
+
(ViewGroup) currentActivity.getWindow().getDecorView().getRootView();
|
|
72
|
+
// The following calls edit the view hierarchy so they must run on the UI thread
|
|
73
|
+
UiThreadUtil.runOnUiThread(() -> {
|
|
74
|
+
Creative creative = new Creative(attentiveConfig, rootView);
|
|
75
|
+
creative.trigger();
|
|
76
|
+
});
|
|
77
|
+
} else {
|
|
78
|
+
Log.w(TAG, "Could not trigger the Attentive Creative because the current Activity was null");
|
|
79
|
+
}
|
|
80
|
+
} catch (Exception e) {
|
|
81
|
+
Log.e(TAG, "Exception when triggering the creative: " + e);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@ReactMethod
|
|
86
|
+
public void clearUser() {
|
|
87
|
+
attentiveConfig.clearUser();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@ReactMethod
|
|
91
|
+
public void identify(ReadableMap identifiers) {
|
|
92
|
+
UserIdentifiers.Builder idsBuilder = new UserIdentifiers.Builder();
|
|
93
|
+
if (identifiers.hasKey("phone")) {
|
|
94
|
+
idsBuilder.withPhone(identifiers.getString("phone"));
|
|
95
|
+
}
|
|
96
|
+
if (identifiers.hasKey("email")) {
|
|
97
|
+
idsBuilder.withEmail(identifiers.getString("email"));
|
|
98
|
+
}
|
|
99
|
+
if (identifiers.hasKey("klaviyoId")) {
|
|
100
|
+
idsBuilder.withKlaviyoId(identifiers.getString("klaviyoId"));
|
|
101
|
+
}
|
|
102
|
+
if (identifiers.hasKey("shopifyId")) {
|
|
103
|
+
idsBuilder.withShopifyId(identifiers.getString("shopifyId"));
|
|
104
|
+
}
|
|
105
|
+
if (identifiers.hasKey("clientUserId")) {
|
|
106
|
+
idsBuilder.withClientUserId(identifiers.getString("clientUserId"));
|
|
107
|
+
}
|
|
108
|
+
if (identifiers.hasKey("customIdentifiers")) {
|
|
109
|
+
Map<String, String> customIds = new HashMap<>();
|
|
110
|
+
Map<String, Object> rawCustomIds = identifiers.getMap("customIdentifiers").toHashMap();
|
|
111
|
+
for (Map.Entry<String, Object> entry : rawCustomIds.entrySet()) {
|
|
112
|
+
if (entry.getValue() instanceof String) {
|
|
113
|
+
customIds.put(entry.getKey(), (String) entry.getValue());
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
idsBuilder.withCustomIdentifiers(customIds);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
attentiveConfig.identify(idsBuilder.build());
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@ReactMethod
|
|
123
|
+
public void recordProductViewEvent(ReadableMap productViewAttrs) {
|
|
124
|
+
Log.i(TAG, "Sending product viewed event");
|
|
125
|
+
|
|
126
|
+
List<Item> items = buildItems(productViewAttrs.getArray("items"));
|
|
127
|
+
ProductViewEvent productViewEvent = new ProductViewEvent.Builder(items).build();
|
|
128
|
+
|
|
129
|
+
AttentiveEventTracker.getInstance().recordEvent(productViewEvent);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@ReactMethod
|
|
133
|
+
public void recordPurchaseEvent(ReadableMap purchaseAttrs) {
|
|
134
|
+
Log.i(TAG, "Sending purchase event");
|
|
135
|
+
Order order = new Order.Builder(purchaseAttrs.getMap("order").getString("orderId")).build();
|
|
136
|
+
|
|
137
|
+
List<Item> items = buildItems(purchaseAttrs.getArray("items"));
|
|
138
|
+
PurchaseEvent purchaseEvent = new PurchaseEvent.Builder(items, order).build();
|
|
139
|
+
|
|
140
|
+
AttentiveEventTracker.getInstance().recordEvent(purchaseEvent);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@ReactMethod
|
|
144
|
+
public void recordAddToCartEvent(ReadableMap addToCartAttrs) {
|
|
145
|
+
Log.i(TAG, "Sending add to cart event");
|
|
146
|
+
|
|
147
|
+
List<Item> items = buildItems(addToCartAttrs.getArray("items"));
|
|
148
|
+
AddToCartEvent addToCartEvent = new AddToCartEvent.Builder(items).build();
|
|
149
|
+
|
|
150
|
+
AttentiveEventTracker.getInstance().recordEvent(addToCartEvent);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@ReactMethod
|
|
154
|
+
public void recordCustomEvent(ReadableMap customEventAttrs) {
|
|
155
|
+
Log.i(TAG, "Sending custom event");
|
|
156
|
+
ReadableMap propertiesRawMap = customEventAttrs.getMap("properties");
|
|
157
|
+
if (propertiesRawMap == null) {
|
|
158
|
+
throw new IllegalArgumentException("The CustomEvent 'properties' field cannot be null.");
|
|
159
|
+
}
|
|
160
|
+
Map<String, String> properties = convertToStringMap(propertiesRawMap.toHashMap());
|
|
161
|
+
CustomEvent customEvent = new CustomEvent.Builder(customEventAttrs.getString("type"), properties).build();
|
|
162
|
+
|
|
163
|
+
AttentiveEventTracker.getInstance().recordEvent(customEvent);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private Map<String, String> convertToStringMap(Map<String, Object> inputMap) {
|
|
167
|
+
Map<String, String> outputMap = new HashMap<>();
|
|
168
|
+
for (Map.Entry<String, Object> entry : inputMap.entrySet()) {
|
|
169
|
+
Object entryValue = entry.getValue();
|
|
170
|
+
if (entryValue == null) {
|
|
171
|
+
throw new InvalidParameterException(String.format("The key '%s' has a null value.", entry.getKey()));
|
|
172
|
+
}
|
|
173
|
+
if (entryValue instanceof String) {
|
|
174
|
+
outputMap.put(entry.getKey(), (String) entry.getValue());
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return outputMap;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private List<Item> buildItems(ReadableArray rawItems) {
|
|
182
|
+
List<Item> items = new ArrayList<>();
|
|
183
|
+
for (int i = 0; i < rawItems.size(); i++) {
|
|
184
|
+
ReadableMap rawItem = rawItems.getMap(i);
|
|
185
|
+
|
|
186
|
+
ReadableMap priceMap = rawItem.getMap("price");
|
|
187
|
+
Price price = new Price.Builder(new BigDecimal(priceMap.getString("price")), Currency.getInstance(priceMap.getString("currency"))).build();
|
|
188
|
+
|
|
189
|
+
Item item = new Item.Builder(rawItem.getString("productId"), rawItem.getString("productVariantId"), price).build();
|
|
190
|
+
items.add(item);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return items;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
package com.attentivereactnativesdk;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
|
|
5
|
+
import com.facebook.react.ReactPackage;
|
|
6
|
+
import com.facebook.react.bridge.NativeModule;
|
|
7
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
8
|
+
import com.facebook.react.uimanager.ViewManager;
|
|
9
|
+
|
|
10
|
+
import java.util.ArrayList;
|
|
11
|
+
import java.util.Collections;
|
|
12
|
+
import java.util.List;
|
|
13
|
+
|
|
14
|
+
public class AttentiveReactNativeSdkPackage implements ReactPackage {
|
|
15
|
+
@NonNull
|
|
16
|
+
@Override
|
|
17
|
+
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
|
|
18
|
+
List<NativeModule> modules = new ArrayList<>();
|
|
19
|
+
modules.add(new AttentiveReactNativeSdkModule(reactContext));
|
|
20
|
+
return modules;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@NonNull
|
|
24
|
+
@Override
|
|
25
|
+
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
|
|
26
|
+
return Collections.emptyList();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
|
|
5
|
+
|
|
6
|
+
Pod::Spec.new do |s|
|
|
7
|
+
s.name = "attentive-react-native-sdk"
|
|
8
|
+
s.version = package["version"]
|
|
9
|
+
s.summary = package["description"]
|
|
10
|
+
s.homepage = package["homepage"]
|
|
11
|
+
s.license = package["license"]
|
|
12
|
+
s.authors = package["author"]
|
|
13
|
+
|
|
14
|
+
s.platforms = { :ios => "11.0" }
|
|
15
|
+
s.source = { :git => "https://github.com/attentive-mobile/attentive-react-native-sdk.git", :tag => "#{s.version}" }
|
|
16
|
+
|
|
17
|
+
s.source_files = "ios/**/*.{h,m,mm}"
|
|
18
|
+
|
|
19
|
+
s.dependency 'attentive-ios-sdk', '0.4.0'
|
|
20
|
+
s.dependency "React-Core"
|
|
21
|
+
|
|
22
|
+
# Don't install the dependencies when we run `pod install` in the old architecture.
|
|
23
|
+
if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
|
|
24
|
+
s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
|
|
25
|
+
s.pod_target_xcconfig = {
|
|
26
|
+
"HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
|
|
27
|
+
"OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
|
|
28
|
+
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
|
|
29
|
+
}
|
|
30
|
+
s.dependency "React-Codegen"
|
|
31
|
+
s.dependency "RCT-Folly"
|
|
32
|
+
s.dependency "RCTRequired"
|
|
33
|
+
s.dependency "RCTTypeSafety"
|
|
34
|
+
s.dependency "ReactCommon/turbomodule/core"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//
|
|
2
|
+
// AttentiveReactNativeSdk.h
|
|
3
|
+
// AttentiveReactNativeSdk
|
|
4
|
+
//
|
|
5
|
+
// Created by Wyatt Davis on 2/13/23.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
9
|
+
#import "RNAttentiveReactNativeSdkSpec.h"
|
|
10
|
+
|
|
11
|
+
@interface AttentiveReactNativeSdk : NSObject <NativeAttentiveReactNativeSdkSpec>
|
|
12
|
+
#else
|
|
13
|
+
#import <React/RCTBridgeModule.h>
|
|
14
|
+
|
|
15
|
+
@interface AttentiveReactNativeSdk : NSObject <RCTBridgeModule>
|
|
16
|
+
#endif
|
|
17
|
+
|
|
18
|
+
@end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
//
|
|
2
|
+
// AttentiveReactNativeSdk.m
|
|
3
|
+
// AttentiveReactNativeSdk
|
|
4
|
+
//
|
|
5
|
+
// Created by Wyatt Davis on 2/13/23.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#import "AttentiveReactNativeSdk.h"
|
|
9
|
+
#import "attentive-sdk-umbrella.h"
|
|
10
|
+
|
|
11
|
+
@implementation AttentiveReactNativeSdk {
|
|
12
|
+
ATTNSDK* _sdk;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
RCT_EXPORT_MODULE()
|
|
16
|
+
|
|
17
|
+
RCT_EXPORT_METHOD(initialize:(NSDictionary*)configuration) {
|
|
18
|
+
_sdk = [[ATTNSDK alloc] initWithDomain:configuration[@"attentiveDomain"] mode:configuration[@"mode"]];
|
|
19
|
+
[ATTNEventTracker setupWithSdk:_sdk];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
RCT_EXPORT_METHOD(triggerCreative) {
|
|
23
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
24
|
+
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
|
|
25
|
+
UIView *topView = window.rootViewController.view;
|
|
26
|
+
[self->_sdk trigger:topView];
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
RCT_EXPORT_METHOD(identify:(NSDictionary*)identifiers) {
|
|
31
|
+
// The dictionary already has the correct keys from the React code, so no translating necessary
|
|
32
|
+
[_sdk identify:identifiers];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
RCT_EXPORT_METHOD(clearUser) {
|
|
36
|
+
[_sdk clearUser];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
RCT_EXPORT_METHOD(recordAddToCartEvent:(NSDictionary*)attrs) {
|
|
40
|
+
NSArray* items = [self parseItems:attrs[@"items"]];
|
|
41
|
+
ATTNAddToCartEvent* event = [[ATTNAddToCartEvent alloc] initWithItems:items];
|
|
42
|
+
[[ATTNEventTracker sharedInstance] recordEvent:event];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
RCT_EXPORT_METHOD(recordProductViewEvent:(NSDictionary*)attrs) {
|
|
46
|
+
NSArray* items = [self parseItems:attrs[@"items"]];
|
|
47
|
+
ATTNProductViewEvent* event = [[ATTNProductViewEvent alloc] initWithItems:items];
|
|
48
|
+
[[ATTNEventTracker sharedInstance] recordEvent:event];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
RCT_EXPORT_METHOD(recordPurchaseEvent:(NSDictionary*)attrs) {
|
|
52
|
+
NSArray* items = [self parseItems:attrs[@"items"]];
|
|
53
|
+
ATTNOrder* order = [[ATTNOrder alloc] initWithOrderId:attrs[@"order"][@"id"]];
|
|
54
|
+
ATTNPurchaseEvent* event = [[ATTNPurchaseEvent alloc] initWithItems:items order:order];
|
|
55
|
+
[[ATTNEventTracker sharedInstance] recordEvent:event];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
RCT_EXPORT_METHOD(recordCustomEvent:(NSDictionary*)attrs) {
|
|
59
|
+
ATTNCustomEvent* customEvent = [[ATTNCustomEvent alloc] initWithType:attrs[@"type"] properties:attrs[@"properties"]];
|
|
60
|
+
[[ATTNEventTracker sharedInstance] recordEvent:customEvent];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
- (NSArray*)parseItems:(NSArray*)rawItems {
|
|
64
|
+
NSMutableArray* itemsToReturn = [[NSMutableArray alloc] init];
|
|
65
|
+
for (NSDictionary* rawItem in rawItems) {
|
|
66
|
+
NSDictionary* rawPrice = rawItem[@"price"];
|
|
67
|
+
ATTNPrice* price = [[ATTNPrice alloc] initWithPrice:[[NSDecimalNumber alloc] initWithString:rawPrice[@"price"]] currency:rawPrice[@"currency"]];
|
|
68
|
+
|
|
69
|
+
ATTNItem* item = [[ATTNItem alloc] initWithProductId:rawItem[@"productId"] productVariantId:rawItem[@"productVariantId"] price:price];
|
|
70
|
+
|
|
71
|
+
[itemsToReturn addObject:item];
|
|
72
|
+
}
|
|
73
|
+
return itemsToReturn;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Don't compile this code when we build for the old architecture.
|
|
77
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
78
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
79
|
+
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
80
|
+
{
|
|
81
|
+
return std::make_shared<facebook::react::NativeAttentiveReactNativeSdkSpecJSI>(params);
|
|
82
|
+
}
|
|
83
|
+
#endif
|
|
84
|
+
|
|
85
|
+
@end
|