From b45c01af582dec8d4ebb6c7723c34549923d0be0 Mon Sep 17 00:00:00 2001 From: Arnaud Barisain-Monrose Date: Fri, 22 Feb 2019 08:21:13 +0100 Subject: [PATCH 01/15] dev: fix the development readme for plugin name change --- readme/development.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/readme/development.md b/readme/development.md index d3137b3..7dd4e51 100644 --- a/readme/development.md +++ b/readme/development.md @@ -13,13 +13,14 @@ - Create a `mkdir @bam.tech` - Go into the folder `cd @bam.tech` - Add `local-modules` to your _.gitignore_ -- Clone the batch-push repository `git clone git@github.com:bamlab/react-native-batch-push.git` +- Clone the react-native-batch-push repository `git clone git@github.com:bamlab/react-native-batch-push.git` +- Rename `react-native-batch-push` to `react-native-batch` - Checkout the required branch or a new branch - Run `yarn` to install dependencies ## 3. Run build for development -- Open `@bam.tech/react-native-batch` within VSCode +- Open `local-modules/@bam.tech/react-native-batch` within VSCode - Run the `Task run build task >> tsc: watch` ## 4. Install the plugin on Android @@ -30,7 +31,7 @@ // android/settings.gradle include ':@bam.tech_react-native-batch' -project(':@bam.tech_react-native-batch').projectDir = new File(rootProject.projectDir, '../local-modules/@bam.tech/react-native-batch-push/android') +project(':@bam.tech_react-native-batch').projectDir = new File(rootProject.projectDir, '../local-modules/@bam.tech/react-native-batch/android') ``` ```groovy @@ -121,7 +122,7 @@ defaultConfig { - Open `/ios/.xcworkspace` - Select __ in XCode - Right click on _Librairies_ > _Add files to _ -- Select `/local-modules/@bam.tech/react-native-batch-push/ios/RNBatchPush.xcodeproj` +- Select `/local-modules/@bam.tech/react-native-batch/ios/RNBatchPush.xcodeproj` - In the project window select - _Build Phases_ - _Link Binary With Librairies_ From 6b21b22afb8052c262aacfe418968895c22ae456 Mon Sep 17 00:00:00 2001 From: Arnaud Barisain-Monrose Date: Fri, 22 Feb 2019 08:41:38 +0100 Subject: [PATCH 02/15] doc: rewrite the small icon documentation to use the manifest (more reliable). fixes bamlab/react-native-batch-push#14 --- README.md | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index e9da650..63c1fa9 100644 --- a/README.md +++ b/README.md @@ -115,23 +115,26 @@ BatchPush.registerForRemoteNotifications(); ### Small push notification icon -It is recommended to provide a small notification icon in your `MainActivity.java`: +For better results on Android 5.0 and higher, it is recommended to add a Small Icon and Notification Color. +An icon can be generated using Android Studio's asset generator: as it will be tinted and masked by the system, only the alpha channel matters and will define the shape displayed. It should be of 24x24dp size. +If your notifications shows up in the system statusbar in a white shape, this is what you need to configure. -```java -// push_icon.png in your res/drawable-{dpi} folder -import com.batch.android.Batch; -import android.os.Bundle; -import android.graphics.Color; +This can be configured in the manifest as metadata in the application tag: + +```xml + -... + + + - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + + - Batch.Push.setSmallIconResourceId(R.drawable.push_icon); - Batch.Push.setNotificationsColor(Color.parseColor(getResources().getString(R.color.pushIconBackground))); - } ``` ### Mobile landings and in-app messaging From 4d59033d3ff6fc983df62074eaaddf791d194981 Mon Sep 17 00:00:00 2001 From: Arnaud Barisain-Monrose Date: Fri, 22 Feb 2019 11:02:49 +0100 Subject: [PATCH 03/15] android: fix readmes to prevent "FirebaseAPp --- README.md | 3 ++- readme/development.md | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 63c1fa9..46232ad 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,8 @@ dependencies { implementation "com.google.firebase:firebase-messaging:17.3.4" ... } + +apply plugin: 'com.google.gms.google-services' ``` #### b. Add your Batch key @@ -134,7 +136,6 @@ This can be configured in the manifest as metadata in the application tag: - ``` ### Mobile landings and in-app messaging diff --git a/readme/development.md b/readme/development.md index 7dd4e51..8812596 100644 --- a/readme/development.md +++ b/readme/development.md @@ -83,6 +83,8 @@ dependencies { implementation "com.google.firebase:firebase-messaging:17.3.4" ... } + +apply plugin: 'com.google.gms.google-services' ``` ### c. Add your Batch key @@ -105,7 +107,7 @@ defaultConfig { - Open the `/android` folder with _Android studio_ - Connect your phone to your computer - Run `adb reverse tcp:8081 tcp:8081` -- Tun `yarn start` +- Run `yarn start` - Run the project in debug mode ## 5. Install the plugin on iOS From e222f4867196d026eb79edaa1af31e0aec1fdf81 Mon Sep 17 00:00:00 2001 From: Arnaud Barisain-Monrose Date: Fri, 22 Feb 2019 17:41:11 +0100 Subject: [PATCH 04/15] WIP: copy eventdata implementation from cordova to adapt it --- src/BatchEventData.ts | 154 ++++++++++++++++++++++++++++++++++++++++++ src/BatchUser.ts | 49 ++++++++++++++ 2 files changed, 203 insertions(+) create mode 100644 src/BatchEventData.ts diff --git a/src/BatchEventData.ts b/src/BatchEventData.ts new file mode 100644 index 0000000..1642049 --- /dev/null +++ b/src/BatchEventData.ts @@ -0,0 +1,154 @@ +import { User as UserAction, UserDataOperation } from '../../actions'; +import Consts from '../../consts'; +import { + isBoolean, + isNumber, + isString, + sendToBridge, + writeBatchLog, +} from '../../helpers'; + +export enum TypedEventAttributeType { + String = 's', + Boolean = 'b', + Integer = 'i', + Float = 'f', +} + +export interface ITypedEventAttribute { + type: TypedEventAttributeType; + value: string | boolean | number; +} + +export class BatchEventData { + private _tags: { [key: string]: true }; // tslint:disable-line + private _attributes: { [key: string]: ITypedEventAttribute }; // tslint:disable-line + + constructor() { + this._tags = {}; + this._attributes = {}; + } + + public addTag(tag: string): BatchEventData { + if (typeof tag === 'undefined') { + writeBatchLog(false, 'BatchEventData - A tag is required'); + return this; + } + + if (isString(tag)) { + if (tag.length === 0 || tag.length > Consts.EventDataStringMaxLength) { + writeBatchLog( + false, + "BatchEventData - Tags can't be empty or longer than " + + Consts.EventDataStringMaxLength + + " characters. Ignoring tag '" + + tag + + "'." + ); + return this; + } + } else { + writeBatchLog(false, 'BatchEventData - Tag argument must be a string'); + return this; + } + + if (Object.keys(this._tags).length >= Consts.EventDataMaxTags) { + writeBatchLog( + false, + 'BatchEventData - Event data cannot hold more than ' + + Consts.EventDataMaxTags + + " tags. Ignoring tag: '" + + tag + + "'" + ); + return this; + } + + this._tags[tag.toLowerCase()] = true; + + return this; + } + + public put(key: string, value: string | number | boolean): BatchEventData { + if (!isString(key)) { + writeBatchLog(false, 'BatchEventData - Key must be a string'); + return this; + } + + if (!Consts.AttributeKeyRegexp.test(key || '')) { + writeBatchLog( + false, + "BatchEventData - Invalid key. Please make sure that the key is made of letters, underscores and numbers only (a-zA-Z0-9_). It also can't be longer than 30 characters. Ignoring attribute '" + + key + + "'" + ); + return this; + } + + if (typeof value === 'undefined' || value === null) { + writeBatchLog( + false, + 'BatchEventData - Value cannot be undefined or null' + ); + return this; + } + + key = key.toLowerCase(); + + if ( + Object.keys(this._tags).length >= Consts.EventDataMaxValues && + !this._attributes.hasOwnProperty(key) + ) { + writeBatchLog( + false, + 'BatchEventData - Event data cannot hold more than ' + + Consts.EventDataMaxValues + + " attributes. Ignoring attribute: '" + + key + + "'" + ); + return this; + } + + let typedAttrValue: ITypedEventAttribute | undefined; + + if (isString(value)) { + typedAttrValue = { + type: TypedEventAttributeType.String, + value, + }; + } else if (isNumber(value)) { + typedAttrValue = { + type: + value % 1 === 0 + ? TypedEventAttributeType.Integer + : TypedEventAttributeType.Float, + value, + }; + } else if (isBoolean(value)) { + typedAttrValue = { + type: TypedEventAttributeType.Boolean, + value, + }; + } else { + writeBatchLog( + false, + 'BatchEventData - Invalid attribute value type. Must be a string, number or boolean' + ); + return this; + } + + if (typedAttrValue) { + this._attributes[key] = typedAttrValue; + } + + return this; + } + + public _toInternalRepresentation() { + return { + attributes: this._attributes, + tags: Object.keys(this._tags), + }; + } +} diff --git a/src/BatchUser.ts b/src/BatchUser.ts index ecf3bb7..97b782f 100644 --- a/src/BatchUser.ts +++ b/src/BatchUser.ts @@ -1,7 +1,34 @@ import { NativeModules } from 'react-native'; import { BatchUserEditor } from './BatchUserEditor'; +import { BatchEventData } from './BatchEventData'; + const RNBatch = NativeModules.RNBatch; +/** + * Represents a locations, using lat/lng coordinates + */ +export interface Location { + /** + * Latitude + */ + latitude: number; + + /** + * Longitude + */ + longitude: number; + + /** + * Date of the tracked location + */ + date?: Date; + + /** + * Precision radius in meters + */ + precision?: number; +} + /** * Batch's user module */ @@ -18,4 +45,26 @@ export const BatchUser = { * The profile is not updated until the method `save()` is called */ editor: (): BatchUserEditor => new BatchUserEditor(), + + /** + * Track an event. Batch must be started at some point, or events won't be sent to the server. + * @param name The event name. Must be a string. + * @param label The event label (optional). Must be a string. + * @param data The event data (optional). Must be an object. + */ + trackEvent: (name: string, label?: string, data?: BatchEventData): void => {}, + + /** + * Track a transaction. Batch must be started at some point, or events won't be sent to the server. + * @param amount Transaction's amount. + * @param data The transaction data (optional). Must be an object. + */ + trackTransaction: (amount: number, data?: { [key: string]: any }): void => {}, + + /** + * Track a geolocation update + * You can call this method from any thread. Batch must be started at some point, or location updates won't be sent to the server. + * @param location User location object + */ + trackLocation: (location: Location): void => {}, }; From b036440660c4351adeac7c15aeaf6ebc8331bc0d Mon Sep 17 00:00:00 2001 From: Arnaud Barisain-Monrose Date: Sun, 24 Feb 2019 15:42:52 +0100 Subject: [PATCH 05/15] misc: fix compilation when the outer project also has typescript in it --- package.json | 3 ++- tsconfig.json | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index c4eed92..68f8fca 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ "doc:deploy": "yarn doc:generate && yarn doc:publish" }, "peerDependencies": { - "react-native": "*" + "react-native": "*", + "@types/react-native": "*" }, "dependencies": { "jest": "^24.1.0" diff --git a/tsconfig.json b/tsconfig.json index bab780c..fcb2021 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,9 @@ "declaration": true, "sourceMap": true, "rootDir": "./src", - "outDir": "./dist" + "outDir": "./dist", + "lib": ["es6"], + "types": ["jest"] }, "exclude": ["node_modules", "**/node_modules/*", "dist", "**/*.test.*"] } From 867922d3ae47961ac3f768b1705f7aab9cf5eb4d Mon Sep 17 00:00:00 2001 From: Arnaud Barisain-Monrose Date: Sun, 24 Feb 2019 16:44:07 +0100 Subject: [PATCH 06/15] misc: add a logger --- src/helpers/Logger.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/helpers/Logger.ts diff --git a/src/helpers/Logger.ts b/src/helpers/Logger.ts new file mode 100644 index 0000000..b60c2a6 --- /dev/null +++ b/src/helpers/Logger.ts @@ -0,0 +1,10 @@ +const DEBUG = true; + +export default function Log(debug: boolean, ...message: any[]) { + const args = ['[Batch]'].concat(Array.prototype.slice.call(arguments, 1)); + if (DEBUG === true && debug === true) { + console.debug.apply(console, args); + } else if (debug === false) { + console.log.apply(console, args); + } +} From 7bcc8942ee96f3aa26af3c05b94c4ef0675f5cb3 Mon Sep 17 00:00:00 2001 From: Arnaud Barisain-Monrose Date: Sun, 24 Feb 2019 16:45:53 +0100 Subject: [PATCH 07/15] misc: add type helpers --- src/helpers/TypeHelpers.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/helpers/TypeHelpers.ts diff --git a/src/helpers/TypeHelpers.ts b/src/helpers/TypeHelpers.ts new file mode 100644 index 0000000..13575f4 --- /dev/null +++ b/src/helpers/TypeHelpers.ts @@ -0,0 +1,13 @@ +export function isString(value: any): value is string { + return value instanceof String || typeof value === 'string'; +} + +export function isNumber(value: any): value is number { + return ( + value instanceof Number || (typeof value === 'number' && !isNaN(value)) + ); +} + +export function isBoolean(value: any): value is boolean { + return value instanceof Boolean || typeof value === 'boolean'; +} From 8d2600ee8944937276d712669a88d206c50d3717 Mon Sep 17 00:00:00 2001 From: Arnaud Barisain-Monrose Date: Sun, 24 Feb 2019 16:58:20 +0100 Subject: [PATCH 08/15] plugin: add TS for trackEvent, Transaction and Location --- src/BatchEventData.ts | 41 +++++++++++------------ src/BatchUser.ts | 76 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 92 insertions(+), 25 deletions(-) diff --git a/src/BatchEventData.ts b/src/BatchEventData.ts index 1642049..53eb7be 100644 --- a/src/BatchEventData.ts +++ b/src/BatchEventData.ts @@ -1,12 +1,12 @@ -import { User as UserAction, UserDataOperation } from '../../actions'; -import Consts from '../../consts'; -import { - isBoolean, - isNumber, - isString, - sendToBridge, - writeBatchLog, -} from '../../helpers'; +import Log from './helpers/Logger'; +import { isString, isNumber, isBoolean } from './helpers/TypeHelpers'; + +const Consts = { + AttributeKeyRegexp: /^[a-zA-Z0-9_]{1,30}$/, + EventDataMaxTags: 10, + EventDataMaxValues: 10, + EventDataStringMaxLength: 64, +}; export enum TypedEventAttributeType { String = 's', @@ -31,13 +31,13 @@ export class BatchEventData { public addTag(tag: string): BatchEventData { if (typeof tag === 'undefined') { - writeBatchLog(false, 'BatchEventData - A tag is required'); + Log(false, 'BatchEventData - A tag is required'); return this; } if (isString(tag)) { if (tag.length === 0 || tag.length > Consts.EventDataStringMaxLength) { - writeBatchLog( + Log( false, "BatchEventData - Tags can't be empty or longer than " + Consts.EventDataStringMaxLength + @@ -48,12 +48,12 @@ export class BatchEventData { return this; } } else { - writeBatchLog(false, 'BatchEventData - Tag argument must be a string'); + Log(false, 'BatchEventData - Tag argument must be a string'); return this; } if (Object.keys(this._tags).length >= Consts.EventDataMaxTags) { - writeBatchLog( + Log( false, 'BatchEventData - Event data cannot hold more than ' + Consts.EventDataMaxTags + @@ -71,12 +71,12 @@ export class BatchEventData { public put(key: string, value: string | number | boolean): BatchEventData { if (!isString(key)) { - writeBatchLog(false, 'BatchEventData - Key must be a string'); + Log(false, 'BatchEventData - Key must be a string'); return this; } if (!Consts.AttributeKeyRegexp.test(key || '')) { - writeBatchLog( + Log( false, "BatchEventData - Invalid key. Please make sure that the key is made of letters, underscores and numbers only (a-zA-Z0-9_). It also can't be longer than 30 characters. Ignoring attribute '" + key + @@ -86,10 +86,7 @@ export class BatchEventData { } if (typeof value === 'undefined' || value === null) { - writeBatchLog( - false, - 'BatchEventData - Value cannot be undefined or null' - ); + Log(false, 'BatchEventData - Value cannot be undefined or null'); return this; } @@ -99,7 +96,7 @@ export class BatchEventData { Object.keys(this._tags).length >= Consts.EventDataMaxValues && !this._attributes.hasOwnProperty(key) ) { - writeBatchLog( + Log( false, 'BatchEventData - Event data cannot hold more than ' + Consts.EventDataMaxValues + @@ -131,7 +128,7 @@ export class BatchEventData { value, }; } else { - writeBatchLog( + Log( false, 'BatchEventData - Invalid attribute value type. Must be a string, number or boolean' ); @@ -145,7 +142,7 @@ export class BatchEventData { return this; } - public _toInternalRepresentation() { + protected _toInternalRepresentation() { return { attributes: this._attributes, tags: Object.keys(this._tags), diff --git a/src/BatchUser.ts b/src/BatchUser.ts index 97b782f..e94e165 100644 --- a/src/BatchUser.ts +++ b/src/BatchUser.ts @@ -1,6 +1,8 @@ import { NativeModules } from 'react-native'; import { BatchUserEditor } from './BatchUserEditor'; import { BatchEventData } from './BatchEventData'; +import { isString, isNumber } from './helpers/TypeHelpers'; +import Log from './helpers/Logger'; const RNBatch = NativeModules.RNBatch; @@ -52,19 +54,87 @@ export const BatchUser = { * @param label The event label (optional). Must be a string. * @param data The event data (optional). Must be an object. */ - trackEvent: (name: string, label?: string, data?: BatchEventData): void => {}, + trackEvent: (name: string, label?: string, data?: BatchEventData): void => { + //TODO (arnaud): Check if "isString" really is necessary. Same for data + // Since _toInternalRepresentation is private, we have to resort to this little hack to access the method. + // That syntax keeps the argument type checking, while casting as any would not. + RNBatch.userData_trackEvent( + name, + isString(label) ? label : null, + data instanceof BatchEventData + ? data['_toInternalRepresentation']() + : null + ); + }, /** * Track a transaction. Batch must be started at some point, or events won't be sent to the server. * @param amount Transaction's amount. * @param data The transaction data (optional). Must be an object. */ - trackTransaction: (amount: number, data?: { [key: string]: any }): void => {}, + trackTransaction: (amount: number, data?: { [key: string]: any }): void => { + if (typeof amount === 'undefined') { + Log( + false, + 'BatchUser - Amount must be a valid number. Ignoring transaction.' + ); + return; + } + + if (!isNumber(amount) || isNaN(amount)) { + Log( + false, + 'BatchUser - Amount must be a valid number. Ignoring transaction.' + ); + return; + } + + if (typeof data !== 'object') { + data = null; + } + + RNBatch.userData_trackTransaction(amount, data); + }, /** * Track a geolocation update * You can call this method from any thread. Batch must be started at some point, or location updates won't be sent to the server. * @param location User location object */ - trackLocation: (location: Location): void => {}, + trackLocation: (location: Location): void => { + if (typeof location !== 'object') { + Log(false, 'BatchUser - Invalid trackLocation argument. Skipping.'); + return; + } + + if (typeof location.latitude !== 'number' || isNaN(location.latitude)) { + Log(false, 'BatchUser - Invalid latitude. Skipping.'); + return; + } + + if (typeof location.longitude !== 'number' || isNaN(location.longitude)) { + Log(false, 'BatchUser - Invalid longitude. Skipping.'); + return; + } + + if ( + location.precision && + (typeof location.precision !== 'number' || isNaN(location.precision)) + ) { + Log(false, 'BatchUser - Invalid precision. Skipping.'); + return; + } + + if (location.date && !(location.date instanceof Date)) { + Log(false, 'BatchUser - Invalid date. Skipping.'); + return; + } + + RNBatch.userData_trackLocation({ + date: location.date ? location.date.getTime() : undefined, + latitude: location.latitude, + longitude: location.longitude, + precision: location.precision, + }); + }, }; From ffeb5d30aa9d7afd623afce1d813bb82ecafecfb Mon Sep 17 00:00:00 2001 From: Arnaud Barisain-Monrose Date: Sun, 24 Feb 2019 17:07:34 +0100 Subject: [PATCH 09/15] android: prepare event methods --- .../java/tech/bam/RNBatchPush/RNBatchModule.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/android/src/main/java/tech/bam/RNBatchPush/RNBatchModule.java b/android/src/main/java/tech/bam/RNBatchPush/RNBatchModule.java index d961aa2..2fce42a 100644 --- a/android/src/main/java/tech/bam/RNBatchPush/RNBatchModule.java +++ b/android/src/main/java/tech/bam/RNBatchPush/RNBatchModule.java @@ -287,6 +287,21 @@ public void userData_save(ReadableArray actions) { editor.save(); } + @ReactMethod + public void userData_trackEvent(String name, String label, ReadableMap eventData) { + + } + + @ReactMethod + public void userData_trackTransaction(double amount, ReadableMap data) { + + } + + @ReactMethod + public void userData_trackLocation(ReadableMap location) { + + } + // EVENT LISTENERS @Override From ddd185da59b98e0eea11476b64bbf77bed11b141 Mon Sep 17 00:00:00 2001 From: Arnaud Barisain-Monrose Date: Sun, 24 Feb 2019 17:23:04 +0100 Subject: [PATCH 10/15] android: add event methods implementation (untested) --- .../tech/bam/RNBatchPush/RNBatchModule.java | 22 ++++++++-- .../java/tech/bam/RNBatchPush/RNUtils.java | 41 +++++++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/tech/bam/RNBatchPush/RNBatchModule.java b/android/src/main/java/tech/bam/RNBatchPush/RNBatchModule.java index 2fce42a..046cefd 100644 --- a/android/src/main/java/tech/bam/RNBatchPush/RNBatchModule.java +++ b/android/src/main/java/tech/bam/RNBatchPush/RNBatchModule.java @@ -2,6 +2,7 @@ import android.app.Activity; import android.content.res.Resources; +import android.location.Location; import android.support.annotation.Nullable; import android.util.Log; @@ -12,6 +13,7 @@ import com.batch.android.BatchMessage; import com.batch.android.BatchUserDataEditor; import com.batch.android.Config; +import com.batch.android.json.JSONObject; import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; @@ -288,18 +290,30 @@ public void userData_save(ReadableArray actions) { } @ReactMethod - public void userData_trackEvent(String name, String label, ReadableMap eventData) { - + public void userData_trackEvent(String name, String label, ReadableMap serializedEventData) { + Batch.User.trackEvent(name, label, RNUtils.convertSerializedEventDataToEventData(serializedEventData)); } @ReactMethod public void userData_trackTransaction(double amount, ReadableMap data) { - + Batch.User.trackTransaction(amount, new JSONObject(data.toHashMap())); } @ReactMethod - public void userData_trackLocation(ReadableMap location) { + public void userData_trackLocation(ReadableMap serializedLocation) { + Location nativeLocation = new Location("tech.bam.RNBatchPush"); + nativeLocation.setLatitude(serializedLocation.getDouble("latitude")); + nativeLocation.setLongitude(serializedLocation.getDouble("longitude")); + + if (serializedLocation.hasKey("precision")) { + nativeLocation.setAccuracy((float) serializedLocation.getDouble("precision")); + } + + if (serializedLocation.hasKey("date")) { + nativeLocation.setTime((long) serializedLocation.getDouble("date")); + } + Batch.User.trackLocation(nativeLocation); } // EVENT LISTENERS diff --git a/android/src/main/java/tech/bam/RNBatchPush/RNUtils.java b/android/src/main/java/tech/bam/RNBatchPush/RNUtils.java index c492bab..92bafef 100644 --- a/android/src/main/java/tech/bam/RNBatchPush/RNUtils.java +++ b/android/src/main/java/tech/bam/RNBatchPush/RNUtils.java @@ -1,5 +1,9 @@ package tech.bam.RNBatchPush; +import android.support.annotation.Nullable; +import android.util.Log; + +import com.batch.android.BatchEventData; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMapKeySetIterator; @@ -65,4 +69,41 @@ public static WritableArray convertArrayToWritableArray(Object[] input) { } return output; } + + @Nullable + public static BatchEventData convertSerializedEventDataToEventData(@Nullable ReadableMap serializedEventData) { + if (serializedEventData == null) { + return null; + } + + BatchEventData batchEventData = new BatchEventData(); + ReadableArray tags = serializedEventData.getArray("tags"); + + for (int i = 0; i < tags.size(); i++) { + batchEventData.addTag(tags.getString(i)); + } + + ReadableMap attributes = serializedEventData.getMap("attributes"); + ReadableMapKeySetIterator iterator = attributes.keySetIterator(); + + while (iterator.hasNextKey()) { + String key = iterator.nextKey(); + ReadableMap valueMap = attributes.getMap(key); + + String type = valueMap.getString("type"); + if ("s".equals(type)) { + batchEventData.put(key, valueMap.getString("value")); + } else if ("b".equals(type)) { + batchEventData.put(key, valueMap.getBoolean("value")); + } else if ("i".equals(type)) { + batchEventData.put(key, valueMap.getDouble("value")); + } else if ("f".equals(type)) { + batchEventData.put(key, valueMap.getDouble("value")); + } else { + Log.e("RNBatchPush", "Invalid parameter : Unknown event_data.attributes type (" + type + ")"); + } + } + + return batchEventData; + } } From 54f8b26185a8b633e427859258606d86cb9de956 Mon Sep 17 00:00:00 2001 From: Arnaud Barisain-Monrose Date: Mon, 25 Feb 2019 13:58:06 +0100 Subject: [PATCH 11/15] iOS: add event methods implementation (untested) --- ios/RNBatch.m | 160 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/ios/RNBatch.m b/ios/RNBatch.m index 7ed3b0d..e2891a0 100644 --- a/ios/RNBatch.m +++ b/ios/RNBatch.m @@ -128,6 +128,166 @@ - (id)init { [editor save]; } +// Event tracking + +RCT_EXPORT_METHOD(userData_trackEvent:(NSString*)name label:(NSString*)label data:(NSDictionary*)serializedEventData) +{ + BatchEventData *batchEventData = nil; + + if ([serializedEventData isKindOfClass:[NSDictionary class]]) + { + batchEventData = [BatchEventData new]; + + if (![serializedEventData isKindOfClass:[NSDictionary class]]) + { + NSLog(@"RNBatch: Error while tracking event data: event data should be an object or null"); + return; + } + + NSArray* tags = serializedEventData[@"tags"]; + NSDictionary* attributes = serializedEventData[@"attributes"]; + + if (![tags isKindOfClass:[NSArray class]]) + { + NSLog(@"RNBatch: Error while tracking event data: event data.tags should be an array"); + return; + } + if (![attributes isKindOfClass:[NSDictionary class]]) + { + NSLog(@"RNBatch: Error while tracking event data: event data.attributes should be a dictionnary"); + return; + } + + for (NSString *tag in tags) + { + if (![tag isKindOfClass:[NSString class]]) + { + NSLog(@"RNBatch: Error while tracking event data: event data.tag childrens should all be strings"); + return; + } + [batchEventData addTag:tag]; + } + + for (NSString *key in attributes.allKeys) + { + NSDictionary *typedAttribute = attributes[key]; + if (![typedAttribute isKindOfClass:[NSDictionary class]]) + { + NSLog(@"RNBatch: Error while tracking event data: event data.attributes childrens should all be String/Dictionary tuples"); + return; + } + + NSString *type = typedAttribute[@"type"]; + NSObject *value = typedAttribute[@"value"]; + + if ([@"s" isEqualToString:type]) { + if (![value isKindOfClass:[NSString class]]) + { + NSLog(@"RNBatch: Error while tracking event data: event data.attributes: expected string value, got something else"); + return; + } + [batchEventData putString:(NSString*)value forKey:key]; + } else if ([@"b" isEqualToString:type]) { + if (![value isKindOfClass:[NSNumber class]]) + { + NSLog(@"RNBatch: Error while tracking event data: event data.attributes: expected number (boolean) value, got something else"); + return; + } + [batchEventData putBool:[(NSNumber*)value boolValue] forKey:key]; + } else if ([@"i" isEqualToString:type]) { + if (![value isKindOfClass:[NSNumber class]]) + { + NSLog(@"RNBatch: Error while tracking event data: event data.attributes: expected number (integer) value, got something else"); + return; + } + [batchEventData putInteger:[(NSNumber*)value integerValue] forKey:key]; + } else if ([@"f" isEqualToString:type]) { + if (![value isKindOfClass:[NSNumber class]]) + { + NSLog(@"RNBatch: Error while tracking event data: event data.attributes: expected number (float) value, got something else"); + return; + } + [batchEventData putDouble:[(NSNumber*)value doubleValue] forKey:key]; + } else { + NSLog(@"RNBatch: Error while tracking event data: Unknown event data.attributes type"); + return; + } + } + } + + [BatchUser trackEvent:name withLabel:label associatedData:batchEventData]; +} + +RCT_EXPORT_METHOD(userData_trackTransaction:(double)amount data:(NSDictionary*)rawData) +{ + if (rawData && ![rawData isKindOfClass:[NSDictionary class]]) + { + NSLog(@"RNBatch: trackTransaction data should be an dictionary or nil"); + return; + } + + [BatchUser trackTransactionWithAmount:amount data:rawData]; +} + +RCT_EXPORT_METHOD(userData_trackLocation:(NSDictionary*)serializedLocation) +{ + if (![serializedLocation isKindOfClass:[NSDictionary class]] || [serializedLocation count]==0) + { + NSLog(@"RNBatch: Empty or null parameters for trackLocation"); + return; + } + + NSNumber *latitude = serializedLocation[@"latitude"]; + NSNumber *longitude = serializedLocation[@"longitude"]; + NSNumber *date = serializedLocation[@"date"]; // MS + NSNumber *precision = serializedLocation[@"precision"]; + + if (![latitude isKindOfClass:[NSNumber class]]) + { + NSLog(@"RNBatch: latitude should be a string"); + return; + } + + if (![longitude isKindOfClass:[NSNumber class]]) + { + NSLog(@"RNBatch: longitude should be a string"); + return; + } + + NSTimeInterval ts = 0; + + if (date) + { + if ([date isKindOfClass:[NSNumber class]]) { + ts = [date doubleValue] / 1000.0; + } else { + NSLog(@"RNBatch: date should be an object or undefined"); + return; + } + } + + NSDate *parsedDate = ts != 0 ? [NSDate dateWithTimeIntervalSince1970:ts] : [NSDate date]; + + NSInteger parsedPrecision = 0; + if (precision) + { + if ([precision isKindOfClass:[NSNumber class]]) { + parsedPrecision = [precision integerValue]; + } else { + NSLog(@"RNBatch: precision should be an object or undefined"); + return; + } + } + + [BatchUser trackLocation:[[CLLocation alloc] initWithCoordinate:CLLocationCoordinate2DMake([latitude doubleValue], [longitude doubleValue]) + altitude:0 + horizontalAccuracy:parsedPrecision + verticalAccuracy:-1 + course:0 + speed:0 + timestamp:parsedDate]]; +} + // Inbox module RCT_EXPORT_METHOD(inbox_fetchNotifications:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) From d7b105967ee8c8ff30d15f2fe3cd72e4f97ee05f Mon Sep 17 00:00:00 2001 From: Arnaud Barisain-Monrose Date: Mon, 25 Feb 2019 14:16:54 +0100 Subject: [PATCH 12/15] plugin: export BatchEventData so it can easily be imported --- src/Batch.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Batch.ts b/src/Batch.ts index b0853be..d194440 100644 --- a/src/Batch.ts +++ b/src/Batch.ts @@ -1,4 +1,5 @@ import { NativeModules } from 'react-native'; +export * from './BatchEventData'; export * from './BatchInbox'; export * from './BatchMessaging'; export * from './BatchPush'; From 086fcd07b9bcd490fd185c7de636958875ef6418 Mon Sep 17 00:00:00 2001 From: Arnaud Barisain-Monrose Date: Mon, 25 Feb 2019 14:49:32 +0100 Subject: [PATCH 13/15] plugin: disable debug logs --- src/helpers/Logger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/Logger.ts b/src/helpers/Logger.ts index b60c2a6..56acf82 100644 --- a/src/helpers/Logger.ts +++ b/src/helpers/Logger.ts @@ -1,4 +1,4 @@ -const DEBUG = true; +const DEBUG = false; export default function Log(debug: boolean, ...message: any[]) { const args = ['[Batch]'].concat(Array.prototype.slice.call(arguments, 1)); From e861eb37d8afb99e18d08e66cfcb7bfd724de93b Mon Sep 17 00:00:00 2001 From: Arnaud Barisain-Monrose Date: Mon, 4 Mar 2019 13:05:34 +0100 Subject: [PATCH 14/15] event: rename 'f' type to 'float' to be more explicit --- android/src/main/java/tech/bam/RNBatchPush/RNUtils.java | 2 +- ios/RNBatch.m | 2 +- src/BatchEventData.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/tech/bam/RNBatchPush/RNUtils.java b/android/src/main/java/tech/bam/RNBatchPush/RNUtils.java index 92bafef..32894a5 100644 --- a/android/src/main/java/tech/bam/RNBatchPush/RNUtils.java +++ b/android/src/main/java/tech/bam/RNBatchPush/RNUtils.java @@ -97,7 +97,7 @@ public static BatchEventData convertSerializedEventDataToEventData(@Nullable Rea batchEventData.put(key, valueMap.getBoolean("value")); } else if ("i".equals(type)) { batchEventData.put(key, valueMap.getDouble("value")); - } else if ("f".equals(type)) { + } else if ("float".equals(type)) { batchEventData.put(key, valueMap.getDouble("value")); } else { Log.e("RNBatchPush", "Invalid parameter : Unknown event_data.attributes type (" + type + ")"); diff --git a/ios/RNBatch.m b/ios/RNBatch.m index e2891a0..cb20d5c 100644 --- a/ios/RNBatch.m +++ b/ios/RNBatch.m @@ -201,7 +201,7 @@ - (id)init { return; } [batchEventData putInteger:[(NSNumber*)value integerValue] forKey:key]; - } else if ([@"f" isEqualToString:type]) { + } else if ([@"float" isEqualToString:type]) { if (![value isKindOfClass:[NSNumber class]]) { NSLog(@"RNBatch: Error while tracking event data: event data.attributes: expected number (float) value, got something else"); diff --git a/src/BatchEventData.ts b/src/BatchEventData.ts index 53eb7be..1fc4593 100644 --- a/src/BatchEventData.ts +++ b/src/BatchEventData.ts @@ -12,7 +12,7 @@ export enum TypedEventAttributeType { String = 's', Boolean = 'b', Integer = 'i', - Float = 'f', + Float = 'float', } export interface ITypedEventAttribute { From 9aaa9dd2c3af7a096e0091a29c27aab6b32ee2bd Mon Sep 17 00:00:00 2001 From: Arnaud Barisain-Monrose Date: Mon, 4 Mar 2019 15:19:08 +0100 Subject: [PATCH 15/15] event: make all types more explicit --- android/src/main/java/tech/bam/RNBatchPush/RNUtils.java | 6 +++--- ios/RNBatch.m | 6 +++--- src/BatchEventData.ts | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/android/src/main/java/tech/bam/RNBatchPush/RNUtils.java b/android/src/main/java/tech/bam/RNBatchPush/RNUtils.java index 32894a5..3989748 100644 --- a/android/src/main/java/tech/bam/RNBatchPush/RNUtils.java +++ b/android/src/main/java/tech/bam/RNBatchPush/RNUtils.java @@ -91,11 +91,11 @@ public static BatchEventData convertSerializedEventDataToEventData(@Nullable Rea ReadableMap valueMap = attributes.getMap(key); String type = valueMap.getString("type"); - if ("s".equals(type)) { + if ("string".equals(type)) { batchEventData.put(key, valueMap.getString("value")); - } else if ("b".equals(type)) { + } else if ("boolean".equals(type)) { batchEventData.put(key, valueMap.getBoolean("value")); - } else if ("i".equals(type)) { + } else if ("integer".equals(type)) { batchEventData.put(key, valueMap.getDouble("value")); } else if ("float".equals(type)) { batchEventData.put(key, valueMap.getDouble("value")); diff --git a/ios/RNBatch.m b/ios/RNBatch.m index cb20d5c..87cbe33 100644 --- a/ios/RNBatch.m +++ b/ios/RNBatch.m @@ -180,21 +180,21 @@ - (id)init { NSString *type = typedAttribute[@"type"]; NSObject *value = typedAttribute[@"value"]; - if ([@"s" isEqualToString:type]) { + if ([@"string" isEqualToString:type]) { if (![value isKindOfClass:[NSString class]]) { NSLog(@"RNBatch: Error while tracking event data: event data.attributes: expected string value, got something else"); return; } [batchEventData putString:(NSString*)value forKey:key]; - } else if ([@"b" isEqualToString:type]) { + } else if ([@"boolean" isEqualToString:type]) { if (![value isKindOfClass:[NSNumber class]]) { NSLog(@"RNBatch: Error while tracking event data: event data.attributes: expected number (boolean) value, got something else"); return; } [batchEventData putBool:[(NSNumber*)value boolValue] forKey:key]; - } else if ([@"i" isEqualToString:type]) { + } else if ([@"integer" isEqualToString:type]) { if (![value isKindOfClass:[NSNumber class]]) { NSLog(@"RNBatch: Error while tracking event data: event data.attributes: expected number (integer) value, got something else"); diff --git a/src/BatchEventData.ts b/src/BatchEventData.ts index 1fc4593..e2a3913 100644 --- a/src/BatchEventData.ts +++ b/src/BatchEventData.ts @@ -9,9 +9,9 @@ const Consts = { }; export enum TypedEventAttributeType { - String = 's', - Boolean = 'b', - Integer = 'i', + String = 'string', + Boolean = 'boolean', + Integer = 'integer', Float = 'float', }