├── .gitignore ├── CHANGELOG.md ├── README.md ├── RNMixpanel.xcodeproj └── project.pbxproj ├── RNMixpanel ├── Mixpanel.h ├── MixpanelPeople.h ├── MixpanelType.h ├── RNMixpanel.h └── RNMixpanel.m ├── android ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── kevinejohn │ └── RNMixpanel │ ├── RNMixpanel.java │ └── RNMixpanelModule.java ├── index.d.ts ├── index.js ├── license.md ├── package.json └── react-native-mixpanel.podspec /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | 33 | # node.js 34 | # 35 | node_modules/ 36 | npm-debug.log 37 | *.prefs 38 | android/.project 39 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.2.5 2 | 3 | - [Android] Fix an uncommon issue with this.getCurrentActivity returning null inside of showInAppMessageIfAvailable 4 | - [iOS] Add access to setAppSessionPropertiesIOS 5 | 6 | # 1.2.4 7 | 8 | - [Android] Update Mixpanel Android SDK to 5.8.5 9 | 10 | # 1.1.14 11 | 12 | - [Android] Add check against potential NPE 13 | - Update Readme 14 | 15 | # 1.1.13 16 | 17 | - [Android] Update mixpanel-android SDK to 5.6.8 18 | 19 | # 1.1.12 20 | 21 | - Method createAlias should accept two parameters (https://developer.mixpanel.com/docs/javascript-full-api-reference#section-mixpanel-alias). Second parameter is optional to keep backward compatabiluty. Will fallback to distinctID generated by mixpanel instance. 22 | 23 | # 1.1.11 24 | 25 | - [iOS] Add missing file 26 | 27 | # 1.1.10 28 | 29 | - Fix typo 30 | 31 | # 1.1.9 32 | 33 | - Expose iOS params that can be passed on Mixpanel instance initalization: trackCrashes, automaticPushTracking, launchOptions 34 | - Update Typescript defs and readme 35 | 36 | # 1.1.8 37 | 38 | - Add support for AppleTV 39 | - Add information about Autolinking from RN >= 0.60 40 | - Update podspec to be package.json driven 41 | - Add fine grained in-app message control 42 | - Update iOS SDK to 3.5.0 43 | - Update Android SDK to 5.6.5 44 | - Add opt in / opt out tracking option 45 | - Add push / registration methods 46 | - Update README 47 | - Update Typescript defs 48 | - Fix crash on clearPushRegistrationId 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # DEPRECATED 3 | Please check out the official bindings at https://github.com/mixpanel/mixpanel-react-native 4 | 5 | 6 | ![npm](https://img.shields.io/npm/dt/react-native-mixpanel.svg?style=for-the-badge) 7 | 8 | ![npm](https://img.shields.io/npm/dm/react-native-mixpanel.svg?style=for-the-badge) 9 | 10 | 11 | # Description 12 | React Native wrapper for Mixpanel library, on top of the regular javascript sdk you can normally use, this provides you all the goodies of the native wrapper including notifications, analysis of the operating system, surveys etc.. 13 | 14 | If you'd like to support, you can donate some Ether to this address: 0x4cD5D72FFd950260e47F9e14F45811C5cCDD0283 15 | 16 | # Installation 17 | - Run `npm install react-native-mixpanel --save` 18 | - Run `react-native link react-native-mixpanel` 19 | - (for RN 0.29.1+; otherwise use `rnpm link react-native-mixpanel`) 20 | 21 | ## Additional Step for iOS ## 22 | - Install Mixpanel iOS SDK via either Cocoapods or manually [more info here](https://mixpanel.com/help/reference/ios) 23 | 24 | ## Additional info for Android (version >= 1.1.2) ## 25 | 26 | From version 1.1.2 module uses Mixpanel SDK >= 5.6.0 that requires FCM 27 | 28 | - Migration steps can be found [here](https://github.com/mixpanel/mixpanel-android/releases/tag/v5.5.0) 29 | - Allow sub-classes to override push notifications payload and Support when more than one push provider is used [more info here](https://github.com/mixpanel/mixpanel-android/releases/tag/v5.5.1) 30 | 31 | 32 | # Autolinking and RN >= 0.60 33 | 34 | Autolinking should work out of the box. 35 | 36 | Remember to do: pod install. 37 | 38 | # Manual Installation 39 | 40 | ## Installation iOS ## 41 | 1. `npm install react-native-mixpanel --save` 42 | 2. Install Mixpanel iOS SDK via either Cocoapods or manually [more info here](https://mixpanel.com/help/reference/ios) 43 | 2. In XCode, in the project navigator, right click `Libraries` ➜ `Add Files to [your project's name]` 44 | 3. Go to `node_modules` ➜ `react-native-mixpanel` and add `RNMixpanel.xcodeproj` 45 | 4. In XCode, in the project navigator, select your project. Add `libRNMixpanel.a` to your project's `Build Phases` ➜ `Link Binary With Libraries` 46 | 5. Click `RNMixpanel.xcodeproj` in the project navigator and go the `Build Settings` tab. Make sure 'All' is toggled on (instead of 'Basic'). Look for `Header Search Paths` and make sure it contains both `$(SRCROOT)/../react-native/React` and `$(SRCROOT)/../../React` - mark both as `recursive`. 47 | 6. Run your project (`Cmd+R`) 48 | 49 | ## Installation Android ## 50 | 51 | * In `android/setting.gradle` 52 | 53 | ```gradle 54 | ... 55 | include ':react-native-mixpanel', ':app' 56 | project(':react-native-mixpanel').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-mixpanel/android') 57 | ``` 58 | 59 | * In `android/app/build.gradle` 60 | 61 | ```gradle 62 | ... 63 | dependencies { 64 | ... 65 | compile project(':react-native-mixpanel') 66 | } 67 | ``` 68 | 69 | * register module (in MainActivity.java) 70 | 71 | On newer versions of React Native register module (MainApplication.java): 72 | 73 | ```java 74 | import com.kevinejohn.RNMixpanel.*; // <--- import 75 | 76 | public class MainActivity extends ReactActivity { 77 | ...... 78 | 79 | /** 80 | * A list of packages used by the app. If the app uses additional views 81 | * or modules besides the default ones, add more packages here. 82 | */ 83 | @Override 84 | protected List getPackages() { 85 | return Arrays.asList( 86 | new RNMixpanel(), // <------ add here 87 | new MainReactPackage()); 88 | } 89 | } 90 | ``` 91 | 92 | # Usage 93 | 94 | ```js 95 | // Require the module 96 | var Mixpanel = require('react-native-mixpanel'); 97 | 98 | // Init Mixpanel SDK with your project token 99 | // @param apiToken - your project token 100 | Mixpanel.sharedInstanceWithToken(YOUR_PROJECT_TOKEN); 101 | 102 | // You can also opt out tracking by default (GDPR) 103 | // @param apiToken - your project token 104 | // @param optOutTrackingByDefault - whether or not to be opted out from tracking by default (default value: false) 105 | Mixpanel.sharedInstanceWithToken(YOUR_PROJECT_TOKEN, false); 106 | 107 | // You can also disable trackCrashes 108 | // @param apiToken - your project token 109 | // @param optOutTrackingByDefault - whether or not to be opted out from tracking by default (default value: false) 110 | // @param trackCrashes (iOS only!) - whether or not to track crashes in Mixpanel. may want to disable if you're seeing issues with your crash reporting for either signals or exceptions (default value: true) 111 | Mixpanel.sharedInstanceWithToken(YOUR_PROJECT_TOKEN, false, true); 112 | 113 | // You can also disable automaticPushTracking 114 | // @param apiToken - your project token 115 | // @param optOutTrackingByDefault - whether or not to be opted out from tracking by default (default value: false) 116 | // @param trackCrashes (iOS only!) - whether or not to track crashes in Mixpanel. may want to disable if you're seeing issues with your crash reporting for either signals or exceptions (default value: true) 117 | // @param automaticPushTracking (iOS only!) - whether or not to automatically track pushes sent from Mixpanel (default value: true) 118 | Mixpanel.sharedInstanceWithToken(YOUR_PROJECT_TOKEN, false, true, true); 119 | 120 | // You can also pass launchOptions 121 | // @param apiToken - your project token 122 | // @param optOutTrackingByDefault - whether or not to be opted out from tracking by default (default value: false) 123 | // @param trackCrashes (iOS only!) - whether or not to track crashes in Mixpanel. may want to disable if you're seeing issues with your crash reporting for either signals or exceptions (default value: true) 124 | // @param automaticPushTracking (iOS only!) - whether or not to automatically track pushes sent from Mixpanel (default value: true) 125 | // @param launchOptions (iOS only!) - your application delegate's launchOptions (default value: null) 126 | Mixpanel.sharedInstanceWithToken(YOUR_PROJECT_TOKEN, false, true, true, null); 127 | 128 | // Opt in tracking. 129 | // Use this method to opt in an already opted out user from tracking. People updates and track calls will be sent to Mixpanel after using this method. 130 | Mixpanel.optInTracking(); 131 | 132 | // Opt out tracking. 133 | 134 | // This method is used to opt out tracking. This causes all events and people request no longer to be sent back to the Mixpanel server. 135 | Mixpanel.optOutTracking(); 136 | 137 | // Send and event name with no properties 138 | Mixpanel.track("Event name"); 139 | 140 | // Track event with properties 141 | Mixpanel.trackWithProperties('Click Button', {button_type: 'yellow button', button_text: 'magic button'}); 142 | 143 | // Create Alias from unique id, i.e. create a new mixpanel profile: to call when a user signs up, with a unique id that is not used by another mixpanel profile as param 144 | Mixpanel.createAlias(UNIQUE_ID) 145 | 146 | // OR: 147 | 148 | // Create an alias, which Mixpanel will use to link two distinct_ids going forward (not retroactively). Multiple aliases can map to the same original ID, but not vice-versa. 149 | // Aliases can also be chained - the following is a valid scenario: 150 | Mixpanel.createAlias('new_id', 'existing_id'); 151 | ... 152 | Mixpanel.createAlias('newer_id', 'new_id'); 153 | // If the original ID is not passed in, we will use the current distinct_id - probably the auto-generated GUID. 154 | // Notes: 155 | // The best practice is to call createAlias() when a unique ID is first created for a user (e.g., when a user first registers for 156 | // an account and provides an email address). createAlias() should never be called more than once for a given user, except to chain 157 | // a newer ID to a previously new ID, as described above. 158 | // More info about createAlias: https://developer.mixpanel.com/docs/javascript-full-api-reference#section-mixpanel-alias 159 | 160 | // Identify, i.e. associate to an existing mixpanel profile: to call when a user logs in and is already registered in Mixpanel with this unique id 161 | Mixpanel.identify(UNIQUE_ID) 162 | 163 | // Set People properties (warning: if no mixpanel profile has been assigned to the current user when this method is called, it will automatically create a new mixpanel profile and the user will no longer be anonymous in Mixpanel) 164 | Mixpanel.set({"$email": "elvis@email.com"}); 165 | 166 | // Set People Properties Once (warning: if no mixpanel profile has been assigned to the current user when this method is called, it will automatically create a new mixpanel profile and the user will no longer be anonymous in Mixpanel) 167 | Mixpanel.setOnce({"$email": "elvis@email.com", "Created": new Date().toISOString()}); 168 | 169 | // Add a new Group for this user. 170 | // @param Group key 171 | // @param A valid Mixpanel property type 172 | Mixpanel.setGroup('company', 'mixpanel'); 173 | 174 | // Register the current user into one Group. The Group must be added before setting 175 | // @param Group key 176 | // @param a singular group ID 177 | Mixpanel.setGroup('company', 'mixpanel'); 178 | 179 | // Timing Events 180 | // Sets the start time for an action, for example uploading an image 181 | Mixpanel.timeEvent("Image Upload"); 182 | // to be followed by a tracking event to define the end time 183 | Mixpanel.track("Image Upload"); 184 | 185 | // Register super properties 186 | Mixpanel.registerSuperProperties({"Account type": "Free", "User Type": "Vendor"}); 187 | 188 | // Register super properties Once 189 | Mixpanel.registerSuperPropertiesOnce({"Gender": "Female"}); 190 | 191 | // track Revenue 192 | Mixpanel.trackCharge(399); 193 | 194 | // track with properties 195 | Mixpanel.trackChargeWithProperties(399, {"product": "ACME Wearable tech"}); 196 | 197 | // increment property 198 | Mixpanel.increment("Login Count", 1); 199 | 200 | // Append array to a list property 201 | Mixpanel.append("Lines", ["Simple", "Dashed"]); 202 | 203 | // Merge array to a list property, excluding duplicate values 204 | Mixpanel.union("Lines", ["Dashed", "Custom"]); 205 | 206 | // Android 207 | // Retrieves current Firebase Cloud Messaging token. 208 | // Should only be called after identify(String) has been called. 209 | Mixpanel.getPushRegistrationId(); 210 | 211 | // send push notifications token to Mixpanel 212 | // Android 213 | Mixpanel.setPushRegistrationId("GCM/FCM push token"); 214 | 215 | // Android 216 | // tell Mixpanel which user record in People Analytics should receive the messages when they are sent from the Mixpanel app, 217 | // make sure you call this right after you call `identify` 218 | // Deprecated. 219 | // in 5.5.0. Google Cloud Messaging (GCM) is now deprecated by Google. To enable end-to-end Firebase Cloud Messaging (FCM) from Mixpanel you only need to add the following to your application manifest XML file: 220 | 221 | 222 | // 226 | // 227 | // 228 | // 229 | // 230 | 231 | 232 | 233 | // Make sure you have a valid google-services.json file in your project and firebase messaging is included as a dependency. Example: 234 | 235 | 236 | // buildscript { 237 | // ... 238 | // dependencies { 239 | // classpath 'com.google.gms:google-services:4.1.0' 240 | // ... 241 | // } 242 | // } 243 | 244 | // dependencies { 245 | // implementation 'com.google.firebase:firebase-messaging:17.3.4' 246 | // implementation 'com.mixpanel.android:mixpanel-android:5.5.0' 247 | // } 248 | 249 | // apply plugin: 'com.google.gms.google-services' 250 | 251 | // We recommend you update your Server Key on mixpanel.com from your Firebase console. Legacy server keys are still supported. 252 | Mixpanel.initPushHandling(YOUR_12_DIGIT_GOOGLE_SENDER_ID); 253 | 254 | // Allows to clear super properites 255 | Mixpabel.clearSuperProperties(); 256 | 257 | // Android 258 | // Unregister an android device for push notifications 259 | // With token it will clear a single Firebase Cloud Messaging token from Mixpanel. 260 | // Without token it will clear all current Firebase Cloud Messaging tokens from Mixpanel. 261 | Mixpanel.clearPushRegistrationId(token?: string); 262 | 263 | // iOS 264 | Mixpanel.addPushDeviceToken("APNS push token"); 265 | 266 | // iOS - unregister iOS device, pushDeviceToken = APNS push token 267 | Mixpanel.removePushDeviceToken(pushDeviceToken: string); 268 | 269 | // iOS 270 | // Unregister the given device to receive push notifications. 271 | Mixpanel.removeAllPushDeviceTokens(); 272 | 273 | // iOS 274 | // Allows control of the min/max sessions 275 | Mixpanel.setAppSessionPropertiesIOS({ 276 | minimumSessionDuration: 60, 277 | maximumSessionDuration: 10, 278 | }); 279 | 280 | // Mixpanel reset method (warning: it will also generate a new unique id and call the identify method with it. Thus, the user will not be anonymous in Mixpanel.) 281 | Mixpanel.reset(); 282 | 283 | // get the last distinct id set with identify or, if identify hasn't been 284 | // called, the default mixpanel id for this device. 285 | Mixpanel.getDistinctId(function(id){}) 286 | ``` 287 | 288 | ## Displaying in-app messages ## 289 | 290 | By default, in-app messages are shown to users when the app starts and a message is available to display 291 | This behaviour can be disabled by default, and explicitally triggered at a later time (e.g. after your loading sequence) 292 | 293 | For iOS, in your app delegate, add the following line: 294 | 295 | ``` 296 | // In application:didFinishLaunchingWithOptions: 297 | Mixpanel *mixpanel = [Mixpanel sharedInstanceWithToken:YOUR_MIXPANEL_TOKEN]; 298 | // Turn this off so the message doesn't pop up automatically. 299 | mixpanel.showNotificationOnActive = NO; 300 | ``` 301 | 302 | For Android, add the following to your app mainifest in the `` tag: 303 | 304 | ``` 305 | 306 | ``` 307 | 308 | You can then call the following in your react native application: 309 | 310 | ``` 311 | Mixpanel.showInAppMessageIfAvailable(); 312 | ``` 313 | 314 | More info: https://developer.mixpanel.com/docs/android-inapp-messages 315 | 316 | ## Configure mixpanel urls 317 | 318 | Add server url in `.plist` files in iOS project. 319 | 320 | ``` 321 | com.mixpanel.config.serverURL 322 | https://api-eu.mixpanel.com 323 | ``` 324 | 325 | Add endpoints to `manifest` in your Android project. 326 | 327 | ``` 328 | 329 | 331 | 333 | 335 | 336 | ``` 337 | 338 | ## Notes ## 339 | For more info please have a look at the [official Mixpanel reference](https://mixpanel.com/help/reference/ios) for iOS 340 | -------------------------------------------------------------------------------- /RNMixpanel.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5EAEEA4A1F05561F00C6FED8 /* RNMixpanel.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = FDC64AF31BF1690D0044C1B4 /* RNMixpanel.h */; }; 11 | 5EAEEA4B1F05562C00C6FED8 /* RNMixpanel.m in Sources */ = {isa = PBXBuildFile; fileRef = FDC64AF51BF1690D0044C1B4 /* RNMixpanel.m */; }; 12 | FDC64AF41BF1690D0044C1B4 /* RNMixpanel.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = FDC64AF31BF1690D0044C1B4 /* RNMixpanel.h */; }; 13 | FDC64AF61BF1690D0044C1B4 /* RNMixpanel.m in Sources */ = {isa = PBXBuildFile; fileRef = FDC64AF51BF1690D0044C1B4 /* RNMixpanel.m */; }; 14 | /* End PBXBuildFile section */ 15 | 16 | /* Begin PBXCopyFilesBuildPhase section */ 17 | 5EAEEA3F1F0555B100C6FED8 /* CopyFiles */ = { 18 | isa = PBXCopyFilesBuildPhase; 19 | buildActionMask = 2147483647; 20 | dstPath = "include/$(PRODUCT_NAME)"; 21 | dstSubfolderSpec = 16; 22 | files = ( 23 | 5EAEEA4A1F05561F00C6FED8 /* RNMixpanel.h in CopyFiles */, 24 | ); 25 | runOnlyForDeploymentPostprocessing = 0; 26 | }; 27 | FDC64AEE1BF1690D0044C1B4 /* CopyFiles */ = { 28 | isa = PBXCopyFilesBuildPhase; 29 | buildActionMask = 2147483647; 30 | dstPath = "include/$(PRODUCT_NAME)"; 31 | dstSubfolderSpec = 16; 32 | files = ( 33 | FDC64AF41BF1690D0044C1B4 /* RNMixpanel.h in CopyFiles */, 34 | ); 35 | runOnlyForDeploymentPostprocessing = 0; 36 | }; 37 | /* End PBXCopyFilesBuildPhase section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | 5EAEEA411F0555B100C6FED8 /* libRNMixpanel-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRNMixpanel-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | E7588479238556670081D7FF /* MixpanelType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MixpanelType.h; sourceTree = ""; }; 42 | E78DD5BC2364BED0009197B6 /* MixpanelPeople.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MixpanelPeople.h; sourceTree = ""; }; 43 | FDC64AF01BF1690D0044C1B4 /* libRNMixpanel.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNMixpanel.a; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | FDC64AF31BF1690D0044C1B4 /* RNMixpanel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNMixpanel.h; sourceTree = ""; }; 45 | FDC64AF51BF1690D0044C1B4 /* RNMixpanel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNMixpanel.m; sourceTree = ""; }; 46 | FDDAC2991BF2B40D00E70D3C /* libMixpanel.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libMixpanel.a; path = "/Users/davidescalzo/Desktop/TestMixpanel/ios/Pods/../build/Debug-iphoneos/libMixpanel.a"; sourceTree = ""; }; 47 | FDDAC29C1BF2B43D00E70D3C /* libPods-TestMixpanel.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libPods-TestMixpanel.a"; path = "/Users/davidescalzo/Desktop/TestMixpanel/ios/Pods/../build/Debug-iphoneos/libPods-TestMixpanel.a"; sourceTree = ""; }; 48 | FDDAC2A11BF2B66500E70D3C /* Mixpanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Mixpanel.h; sourceTree = ""; }; 49 | /* End PBXFileReference section */ 50 | 51 | /* Begin PBXFrameworksBuildPhase section */ 52 | 5EAEEA3E1F0555B100C6FED8 /* Frameworks */ = { 53 | isa = PBXFrameworksBuildPhase; 54 | buildActionMask = 2147483647; 55 | files = ( 56 | ); 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | FDC64AED1BF1690D0044C1B4 /* Frameworks */ = { 60 | isa = PBXFrameworksBuildPhase; 61 | buildActionMask = 2147483647; 62 | files = ( 63 | ); 64 | runOnlyForDeploymentPostprocessing = 0; 65 | }; 66 | /* End PBXFrameworksBuildPhase section */ 67 | 68 | /* Begin PBXGroup section */ 69 | FDC64AE71BF1690D0044C1B4 = { 70 | isa = PBXGroup; 71 | children = ( 72 | FDDAC29C1BF2B43D00E70D3C /* libPods-TestMixpanel.a */, 73 | FDDAC2991BF2B40D00E70D3C /* libMixpanel.a */, 74 | FDC64AF21BF1690D0044C1B4 /* RNMixpanel */, 75 | FDC64AF11BF1690D0044C1B4 /* Products */, 76 | ); 77 | sourceTree = ""; 78 | }; 79 | FDC64AF11BF1690D0044C1B4 /* Products */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | FDC64AF01BF1690D0044C1B4 /* libRNMixpanel.a */, 83 | 5EAEEA411F0555B100C6FED8 /* libRNMixpanel-tvOS.a */, 84 | ); 85 | name = Products; 86 | sourceTree = ""; 87 | }; 88 | FDC64AF21BF1690D0044C1B4 /* RNMixpanel */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | E78DD5BC2364BED0009197B6 /* MixpanelPeople.h */, 92 | E7588479238556670081D7FF /* MixpanelType.h */, 93 | FDDAC2A11BF2B66500E70D3C /* Mixpanel.h */, 94 | FDC64AF31BF1690D0044C1B4 /* RNMixpanel.h */, 95 | FDC64AF51BF1690D0044C1B4 /* RNMixpanel.m */, 96 | ); 97 | path = RNMixpanel; 98 | sourceTree = ""; 99 | }; 100 | /* End PBXGroup section */ 101 | 102 | /* Begin PBXNativeTarget section */ 103 | 5EAEEA401F0555B100C6FED8 /* RNMixpanel-tvOS */ = { 104 | isa = PBXNativeTarget; 105 | buildConfigurationList = 5EAEEA491F0555B100C6FED8 /* Build configuration list for PBXNativeTarget "RNMixpanel-tvOS" */; 106 | buildPhases = ( 107 | 5EAEEA3D1F0555B100C6FED8 /* Sources */, 108 | 5EAEEA3E1F0555B100C6FED8 /* Frameworks */, 109 | 5EAEEA3F1F0555B100C6FED8 /* CopyFiles */, 110 | ); 111 | buildRules = ( 112 | ); 113 | dependencies = ( 114 | ); 115 | name = "RNMixpanel-tvOS"; 116 | productName = "RNMixpanel-tvOS"; 117 | productReference = 5EAEEA411F0555B100C6FED8 /* libRNMixpanel-tvOS.a */; 118 | productType = "com.apple.product-type.library.static"; 119 | }; 120 | FDC64AEF1BF1690D0044C1B4 /* RNMixpanel */ = { 121 | isa = PBXNativeTarget; 122 | buildConfigurationList = FDC64AF91BF1690D0044C1B4 /* Build configuration list for PBXNativeTarget "RNMixpanel" */; 123 | buildPhases = ( 124 | FDC64AEC1BF1690D0044C1B4 /* Sources */, 125 | FDC64AED1BF1690D0044C1B4 /* Frameworks */, 126 | FDC64AEE1BF1690D0044C1B4 /* CopyFiles */, 127 | ); 128 | buildRules = ( 129 | ); 130 | dependencies = ( 131 | ); 132 | name = RNMixpanel; 133 | productName = RNMixpanel; 134 | productReference = FDC64AF01BF1690D0044C1B4 /* libRNMixpanel.a */; 135 | productType = "com.apple.product-type.library.static"; 136 | }; 137 | /* End PBXNativeTarget section */ 138 | 139 | /* Begin PBXProject section */ 140 | FDC64AE81BF1690D0044C1B4 /* Project object */ = { 141 | isa = PBXProject; 142 | attributes = { 143 | LastUpgradeCheck = 0710; 144 | ORGANIZATIONNAME = "Davide Scalzo"; 145 | TargetAttributes = { 146 | 5EAEEA401F0555B100C6FED8 = { 147 | CreatedOnToolsVersion = 8.3.3; 148 | ProvisioningStyle = Automatic; 149 | }; 150 | FDC64AEF1BF1690D0044C1B4 = { 151 | CreatedOnToolsVersion = 7.1; 152 | }; 153 | }; 154 | }; 155 | buildConfigurationList = FDC64AEB1BF1690D0044C1B4 /* Build configuration list for PBXProject "RNMixpanel" */; 156 | compatibilityVersion = "Xcode 3.2"; 157 | developmentRegion = English; 158 | hasScannedForEncodings = 0; 159 | knownRegions = ( 160 | English, 161 | en, 162 | ); 163 | mainGroup = FDC64AE71BF1690D0044C1B4; 164 | productRefGroup = FDC64AF11BF1690D0044C1B4 /* Products */; 165 | projectDirPath = ""; 166 | projectRoot = ""; 167 | targets = ( 168 | FDC64AEF1BF1690D0044C1B4 /* RNMixpanel */, 169 | 5EAEEA401F0555B100C6FED8 /* RNMixpanel-tvOS */, 170 | ); 171 | }; 172 | /* End PBXProject section */ 173 | 174 | /* Begin PBXSourcesBuildPhase section */ 175 | 5EAEEA3D1F0555B100C6FED8 /* Sources */ = { 176 | isa = PBXSourcesBuildPhase; 177 | buildActionMask = 2147483647; 178 | files = ( 179 | 5EAEEA4B1F05562C00C6FED8 /* RNMixpanel.m in Sources */, 180 | ); 181 | runOnlyForDeploymentPostprocessing = 0; 182 | }; 183 | FDC64AEC1BF1690D0044C1B4 /* Sources */ = { 184 | isa = PBXSourcesBuildPhase; 185 | buildActionMask = 2147483647; 186 | files = ( 187 | FDC64AF61BF1690D0044C1B4 /* RNMixpanel.m in Sources */, 188 | ); 189 | runOnlyForDeploymentPostprocessing = 0; 190 | }; 191 | /* End PBXSourcesBuildPhase section */ 192 | 193 | /* Begin XCBuildConfiguration section */ 194 | 5EAEEA471F0555B100C6FED8 /* Debug */ = { 195 | isa = XCBuildConfiguration; 196 | buildSettings = { 197 | CLANG_ANALYZER_NONNULL = YES; 198 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 199 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 200 | CLANG_WARN_INFINITE_RECURSION = YES; 201 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 202 | DEBUG_INFORMATION_FORMAT = dwarf; 203 | ENABLE_TESTABILITY = YES; 204 | OTHER_LDFLAGS = "-ObjC"; 205 | PRODUCT_NAME = "$(TARGET_NAME)"; 206 | SDKROOT = appletvos; 207 | SKIP_INSTALL = YES; 208 | TVOS_DEPLOYMENT_TARGET = 10.2; 209 | }; 210 | name = Debug; 211 | }; 212 | 5EAEEA481F0555B100C6FED8 /* Release */ = { 213 | isa = XCBuildConfiguration; 214 | buildSettings = { 215 | CLANG_ANALYZER_NONNULL = YES; 216 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 217 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 218 | CLANG_WARN_INFINITE_RECURSION = YES; 219 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 220 | COPY_PHASE_STRIP = NO; 221 | OTHER_LDFLAGS = "-ObjC"; 222 | PRODUCT_NAME = "$(TARGET_NAME)"; 223 | SDKROOT = appletvos; 224 | SKIP_INSTALL = YES; 225 | TVOS_DEPLOYMENT_TARGET = 10.2; 226 | }; 227 | name = Release; 228 | }; 229 | FDC64AF71BF1690D0044C1B4 /* Debug */ = { 230 | isa = XCBuildConfiguration; 231 | buildSettings = { 232 | ALWAYS_SEARCH_USER_PATHS = NO; 233 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 234 | CLANG_CXX_LIBRARY = "libc++"; 235 | CLANG_ENABLE_MODULES = YES; 236 | CLANG_ENABLE_OBJC_ARC = YES; 237 | CLANG_WARN_BOOL_CONVERSION = YES; 238 | CLANG_WARN_CONSTANT_CONVERSION = YES; 239 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 240 | CLANG_WARN_EMPTY_BODY = YES; 241 | CLANG_WARN_ENUM_CONVERSION = YES; 242 | CLANG_WARN_INT_CONVERSION = YES; 243 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 244 | CLANG_WARN_UNREACHABLE_CODE = YES; 245 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 246 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 247 | COPY_PHASE_STRIP = NO; 248 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 249 | ENABLE_STRICT_OBJC_MSGSEND = YES; 250 | ENABLE_TESTABILITY = NO; 251 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 252 | GCC_C_LANGUAGE_STANDARD = gnu99; 253 | GCC_DYNAMIC_NO_PIC = NO; 254 | GCC_NO_COMMON_BLOCKS = YES; 255 | GCC_OPTIMIZATION_LEVEL = 0; 256 | GCC_PREPROCESSOR_DEFINITIONS = ( 257 | "DEBUG=1", 258 | "$(inherited)", 259 | ); 260 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 261 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 262 | GCC_WARN_UNDECLARED_SELECTOR = YES; 263 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 264 | GCC_WARN_UNUSED_FUNCTION = YES; 265 | GCC_WARN_UNUSED_VARIABLE = YES; 266 | HEADER_SEARCH_PATHS = "$(inherited)"; 267 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 268 | MTL_ENABLE_DEBUG_INFO = YES; 269 | ONLY_ACTIVE_ARCH = YES; 270 | SDKROOT = iphoneos; 271 | }; 272 | name = Debug; 273 | }; 274 | FDC64AF81BF1690D0044C1B4 /* Release */ = { 275 | isa = XCBuildConfiguration; 276 | buildSettings = { 277 | ALWAYS_SEARCH_USER_PATHS = NO; 278 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 279 | CLANG_CXX_LIBRARY = "libc++"; 280 | CLANG_ENABLE_MODULES = YES; 281 | CLANG_ENABLE_OBJC_ARC = YES; 282 | CLANG_WARN_BOOL_CONVERSION = YES; 283 | CLANG_WARN_CONSTANT_CONVERSION = YES; 284 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 285 | CLANG_WARN_EMPTY_BODY = YES; 286 | CLANG_WARN_ENUM_CONVERSION = YES; 287 | CLANG_WARN_INT_CONVERSION = YES; 288 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 289 | CLANG_WARN_UNREACHABLE_CODE = YES; 290 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 291 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 292 | COPY_PHASE_STRIP = YES; 293 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 294 | ENABLE_NS_ASSERTIONS = NO; 295 | ENABLE_STRICT_OBJC_MSGSEND = YES; 296 | ENABLE_TESTABILITY = NO; 297 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 298 | GCC_C_LANGUAGE_STANDARD = gnu99; 299 | GCC_NO_COMMON_BLOCKS = YES; 300 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 301 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 302 | GCC_WARN_UNDECLARED_SELECTOR = YES; 303 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 304 | GCC_WARN_UNUSED_FUNCTION = YES; 305 | GCC_WARN_UNUSED_VARIABLE = YES; 306 | HEADER_SEARCH_PATHS = "$(inherited)"; 307 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 308 | MTL_ENABLE_DEBUG_INFO = NO; 309 | SDKROOT = iphoneos; 310 | VALIDATE_PRODUCT = YES; 311 | }; 312 | name = Release; 313 | }; 314 | FDC64AFA1BF1690D0044C1B4 /* Debug */ = { 315 | isa = XCBuildConfiguration; 316 | buildSettings = { 317 | HEADER_SEARCH_PATHS = ( 318 | "$(inherited)", 319 | "$(SRCROOT)/../../React/**", 320 | "$(SRCROOT)/../../node_modules/react-native/React/**", 321 | ); 322 | OTHER_LDFLAGS = "-ObjC"; 323 | PRODUCT_NAME = "$(TARGET_NAME)"; 324 | SKIP_INSTALL = YES; 325 | }; 326 | name = Debug; 327 | }; 328 | FDC64AFB1BF1690D0044C1B4 /* Release */ = { 329 | isa = XCBuildConfiguration; 330 | buildSettings = { 331 | HEADER_SEARCH_PATHS = ( 332 | "$(inherited)", 333 | "$(SRCROOT)/../../React/**", 334 | "$(SRCROOT)/../../node_modules/react-native/React/**", 335 | ); 336 | OTHER_LDFLAGS = "-ObjC"; 337 | PRODUCT_NAME = "$(TARGET_NAME)"; 338 | SKIP_INSTALL = YES; 339 | }; 340 | name = Release; 341 | }; 342 | /* End XCBuildConfiguration section */ 343 | 344 | /* Begin XCConfigurationList section */ 345 | 5EAEEA491F0555B100C6FED8 /* Build configuration list for PBXNativeTarget "RNMixpanel-tvOS" */ = { 346 | isa = XCConfigurationList; 347 | buildConfigurations = ( 348 | 5EAEEA471F0555B100C6FED8 /* Debug */, 349 | 5EAEEA481F0555B100C6FED8 /* Release */, 350 | ); 351 | defaultConfigurationIsVisible = 0; 352 | defaultConfigurationName = Release; 353 | }; 354 | FDC64AEB1BF1690D0044C1B4 /* Build configuration list for PBXProject "RNMixpanel" */ = { 355 | isa = XCConfigurationList; 356 | buildConfigurations = ( 357 | FDC64AF71BF1690D0044C1B4 /* Debug */, 358 | FDC64AF81BF1690D0044C1B4 /* Release */, 359 | ); 360 | defaultConfigurationIsVisible = 0; 361 | defaultConfigurationName = Release; 362 | }; 363 | FDC64AF91BF1690D0044C1B4 /* Build configuration list for PBXNativeTarget "RNMixpanel" */ = { 364 | isa = XCConfigurationList; 365 | buildConfigurations = ( 366 | FDC64AFA1BF1690D0044C1B4 /* Debug */, 367 | FDC64AFB1BF1690D0044C1B4 /* Release */, 368 | ); 369 | defaultConfigurationIsVisible = 0; 370 | defaultConfigurationName = Release; 371 | }; 372 | /* End XCConfigurationList section */ 373 | }; 374 | rootObject = FDC64AE81BF1690D0044C1B4 /* Project object */; 375 | } 376 | -------------------------------------------------------------------------------- /RNMixpanel/Mixpanel.h: -------------------------------------------------------------------------------- 1 | #import 2 | #if !TARGET_OS_OSX 3 | #import 4 | #else 5 | #import 6 | #endif 7 | #import "MixpanelPeople.h" 8 | #import "MixpanelType.h" 9 | 10 | 11 | #if defined(MIXPANEL_WATCHOS) 12 | #define MIXPANEL_FLUSH_IMMEDIATELY 1 13 | #define MIXPANEL_NO_APP_LIFECYCLE_SUPPORT 1 14 | #endif 15 | 16 | #if (defined(MIXPANEL_WATCHOS) || defined(MIXPANEL_MACOS)) 17 | #define MIXPANEL_NO_UIAPPLICATION_ACCESS 1 18 | #endif 19 | 20 | #if (defined(MIXPANEL_TVOS) || defined(MIXPANEL_WATCHOS) || defined(MIXPANEL_MACOS)) 21 | #define MIXPANEL_NO_REACHABILITY_SUPPORT 1 22 | #define MIXPANEL_NO_AUTOMATIC_EVENTS_SUPPORT 1 23 | #define MIXPANEL_NO_NOTIFICATION_AB_TEST_SUPPORT 1 24 | #define MIXPANEL_NO_CONNECT_INTEGRATION_SUPPORT 1 25 | #endif 26 | 27 | @class MixpanelPeople; 28 | @class MixpanelGroup; 29 | @protocol MixpanelDelegate; 30 | 31 | NS_ASSUME_NONNULL_BEGIN 32 | 33 | /*! 34 | A string constant "mini" that respresent Mini Notification 35 | */ 36 | extern NSString *const MPNotificationTypeMini; 37 | /*! 38 | A string constant "takeover" that respresent Takeover Notification 39 | */ 40 | extern NSString *const MPNotificationTypeTakeover; 41 | 42 | /*! 43 | Mixpanel API. 44 | 45 | The primary interface for integrating Mixpanel with your app. 46 | 47 | Use the Mixpanel class to set up your project and track events in Mixpanel 48 | Engagement. It now also includes a people property for accessing 49 | the Mixpanel People API. 50 | 51 |
 52 |  // Initialize the API
 53 |  Mixpanel *mixpanel = [Mixpanel sharedInstanceWithToken:@"YOUR API TOKEN"];
 54 | 
 55 |  // Track an event in Mixpanel Engagement
 56 |  [mixpanel track:@"Button Clicked"];
 57 | 
 58 |  // Set properties on a user in Mixpanel People
 59 |  [mixpanel identify:@"CURRENT USER DISTINCT ID"];
 60 |  [mixpanel.people set:@"Plan" to:@"Premium"];
 61 |  
62 | 63 | For more advanced usage, please see the Mixpanel iPhone 65 | Library Guide. 66 | */ 67 | @interface Mixpanel : NSObject 68 | 69 | #pragma mark Properties 70 | 71 | /*! 72 | Accessor to the Mixpanel People API object. 73 | 74 | See the documentation for MixpanelDelegate below for more information. 75 | */ 76 | @property (atomic, readonly, strong) MixpanelPeople *people; 77 | 78 | /*! 79 | The distinct ID of the current user. 80 | 81 | A distinct ID is a string that uniquely identifies one of your users. By default, 82 | we'll use the device's advertisingIdentifier UUIDString, if that is not available 83 | we'll use the device's identifierForVendor UUIDString, and finally if that 84 | is not available we will generate a new random UUIDString. To change the 85 | current distinct ID, use the identify: method. 86 | */ 87 | @property (atomic, readonly, copy) NSString *distinctId; 88 | 89 | /*! 90 | The default anonymous Id / distinct Id given to the events before identify. 91 | 92 | A default distinct ID is a string that uniquely identifies the anonymous activity. 93 | By default, we'll use the device's advertisingIdentifier UUIDString, if that is not 94 | available we'll use the device's identifierForVendor UUIDString, and finally if that 95 | is not available we will generate a new random UUIDString. 96 | */ 97 | @property (atomic, readonly, copy) NSString *anonymousId; 98 | 99 | /*! 100 | The user ID with which identify: is called with. 101 | 102 | This is null until identify: is called and is set to the id 103 | with which identify is called with. 104 | */ 105 | @property (atomic, readonly, copy) NSString *userId; 106 | 107 | /*! 108 | The alias of the current user. 109 | 110 | An alias is another string that uniquely identifies one of your users. Typically, 111 | this is the user ID from your database. By using an alias you can link pre- and 112 | post-sign up activity as well as cross-platform activity under one distinct ID. 113 | To set the alias use the createAlias:forDistinctID: method. 114 | */ 115 | @property (atomic, readonly, copy) NSString *alias; 116 | 117 | /*! 118 | A flag which says if a distinctId is already in peristence from old sdk 119 | Defaults to NO. 120 | */ 121 | @property (atomic) BOOL hadPersistedDistinctId; 122 | 123 | /*! 124 | The base URL used for Mixpanel API requests. 125 | 126 | Useful if you need to proxy Mixpanel requests. Defaults to 127 | https://api.mixpanel.com. 128 | */ 129 | @property (nonatomic, copy) NSString *serverURL; 130 | 131 | /*! 132 | Flush timer's interval. 133 | 134 | Setting a flush interval of 0 will turn off the flush timer. 135 | */ 136 | @property (atomic) NSUInteger flushInterval; 137 | 138 | /*! 139 | Control whether the library should flush data to Mixpanel when the app 140 | enters the background. 141 | 142 | Defaults to YES. Only affects apps targeted at iOS 4.0, when background 143 | task support was introduced, and later. 144 | */ 145 | @property (atomic) BOOL flushOnBackground; 146 | 147 | /*! 148 | Controls whether to show spinning network activity indicator when flushing 149 | data to the Mixpanel servers. 150 | 151 | Defaults to YES. 152 | */ 153 | @property (atomic) BOOL shouldManageNetworkActivityIndicator; 154 | 155 | /*! 156 | Controls whether to automatically check for notifications for the 157 | currently identified user when the application becomes active. 158 | 159 | Defaults to YES. Will fire a network request on 160 | applicationDidBecomeActive to retrieve a list of valid notifications 161 | for the currently identified user. 162 | */ 163 | @property (atomic) BOOL checkForNotificationsOnActive; 164 | 165 | /*! 166 | Controls whether to automatically check for A/B test variants for the 167 | currently identified user when the application becomes active. 168 | 169 | Defaults to YES. Will fire a network request on 170 | applicationDidBecomeActive to retrieve a list of valid variants 171 | for the currently identified user. 172 | */ 173 | @property (atomic) BOOL checkForVariantsOnActive; 174 | 175 | /*! 176 | Controls whether to automatically check for and show in-app notifications 177 | for the currently identified user when the application becomes active. 178 | 179 | Defaults to YES. 180 | */ 181 | @property (atomic) BOOL showNotificationOnActive; 182 | 183 | /*! 184 | Controls whether to automatically send the client IP Address as part of 185 | event tracking. With an IP address, geo-location is possible down to neighborhoods 186 | within a city, although the Mixpanel Dashboard will just show you city level location 187 | specificity. For privacy reasons, you may be in a situation where you need to forego 188 | effectively having access to such granular location information via the IP Address. 189 | 190 | Defaults to YES. 191 | */ 192 | @property (atomic) BOOL useIPAddressForGeoLocation; 193 | 194 | /*! 195 | Controls whether to enable the visual test designer for A/B testing and codeless on mixpanel.com. 196 | You will be unable to edit A/B tests and codeless events with this disabled, however *previously* 197 | created A/B tests and codeless events will still be delivered. 198 | 199 | Defaults to YES. 200 | */ 201 | @property (atomic) BOOL enableVisualABTestAndCodeless; 202 | 203 | /*! 204 | Controls whether to enable the run time debug logging at all levels. Note that the 205 | Mixpanel SDK uses Apple System Logging to forward log messages to `STDERR`, this also 206 | means that mixpanel logs are segmented by log level. Settings this to `YES` will enable 207 | Mixpanel logging at the following levels: 208 | 209 | * Error - Something has failed 210 | * Warning - Something is amiss and might fail if not corrected 211 | * Info - The lowest priority that is normally logged, purely informational in nature 212 | * Debug - Information useful only to developers, and normally not logged. 213 | 214 | 215 | Defaults to NO. 216 | */ 217 | @property (atomic) BOOL enableLogging; 218 | 219 | /*! 220 | Determines the time, in seconds, that a mini notification will remain on 221 | the screen before automatically hiding itself. 222 | 223 | Defaults to 6.0. 224 | */ 225 | @property (atomic) CGFloat miniNotificationPresentationTime; 226 | 227 | #if !MIXPANEL_NO_AUTOMATIC_EVENTS_SUPPORT 228 | /*! 229 | The minimum session duration (ms) that is tracked in automatic events. 230 | 231 | The default value is 10000 (10 seconds). 232 | */ 233 | @property (atomic) UInt64 minimumSessionDuration; 234 | 235 | /*! 236 | The maximum session duration (ms) that is tracked in automatic events. 237 | 238 | The default value is UINT64_MAX (no maximum session duration). 239 | */ 240 | @property (atomic) UInt64 maximumSessionDuration; 241 | #endif 242 | 243 | /*! 244 | The a MixpanelDelegate object that can be used to assert fine-grain control 245 | over Mixpanel network activity. 246 | 247 | Using a delegate is optional. See the documentation for MixpanelDelegate 248 | below for more information. 249 | */ 250 | @property (atomic, weak) id delegate; // allows fine grain control over uploading (optional) 251 | 252 | #pragma mark Tracking 253 | 254 | /*! 255 | Returns (and creates, if needed) a singleton instance of the API. 256 | 257 | This method will return a singleton instance of the Mixpanel class for 258 | you using the given project token. If an instance does not exist, this method will create 259 | one using initWithToken:launchOptions:andFlushInterval:. If you only have one 260 | instance in your project, you can use sharedInstance to retrieve it. 261 | 262 |
263 |  [Mixpanel sharedInstance] track:@"Something Happened"]];
264 |  
265 | 266 | If you are going to use this singleton approach, 267 | sharedInstanceWithToken: must be the first call to the 268 | Mixpanel class, since it performs important initializations to 269 | the API. 270 | 271 | @param apiToken your project token 272 | */ 273 | + (Mixpanel *)sharedInstanceWithToken:(NSString *)apiToken; 274 | 275 | /*! 276 | Initializes a singleton instance of the API, uses it to set whether or not to opt out tracking for 277 | GDPR compliance, and then returns it. 278 | 279 | This is the preferred method for creating a sharedInstance with a mixpanel 280 | like above. With the optOutTrackingByDefault parameter, Mixpanel tracking can be opted out by default. 281 | 282 | @param apiToken your project token 283 | @param optOutTrackingByDefault whether or not to be opted out from tracking by default 284 | 285 | */ 286 | + (Mixpanel *)sharedInstanceWithToken:(NSString *)apiToken optOutTrackingByDefault:(BOOL)optOutTrackingByDefault; 287 | 288 | /*! 289 | Initializes a singleton instance of the API, uses it to track launchOptions information, 290 | and then returns it. 291 | 292 | This is the preferred method for creating a sharedInstance with a mixpanel 293 | like above. With the launchOptions parameter, Mixpanel can track referral 294 | information created by push notifications. 295 | 296 | @param apiToken your project token 297 | @param launchOptions your application delegate's launchOptions 298 | 299 | */ 300 | + (Mixpanel *)sharedInstanceWithToken:(NSString *)apiToken launchOptions:(nullable NSDictionary *)launchOptions; 301 | 302 | /*! 303 | Initializes a singleton instance of the API, uses it to track launchOptions information, 304 | and then returns it. 305 | 306 | This is the preferred method for creating a sharedInstance with a mixpanel 307 | like above. With the trackCrashes and automaticPushTracking parameter, Mixpanel can track crashes and automatic push. 308 | 309 | @param apiToken your project token 310 | @param launchOptions your application delegate's launchOptions 311 | @param trackCrashes whether or not to track crashes in Mixpanel. may want to disable if you're seeing 312 | issues with your crash reporting for either signals or exceptions 313 | @param automaticPushTracking whether or not to automatically track pushes sent from Mixpanel 314 | */ 315 | + (Mixpanel *)sharedInstanceWithToken:(NSString *)apiToken launchOptions:(nullable NSDictionary *)launchOptions trackCrashes:(BOOL)trackCrashes automaticPushTracking:(BOOL)automaticPushTracking; 316 | 317 | /*! 318 | Initializes a singleton instance of the API, uses it to track launchOptions information, 319 | and then returns it. 320 | 321 | This is the preferred method for creating a sharedInstance with a mixpanel 322 | like above. With the optOutTrackingByDefault parameter, Mixpanel tracking can be opted out by default. 323 | 324 | @param apiToken your project token 325 | @param launchOptions your application delegate's launchOptions 326 | @param trackCrashes whether or not to track crashes in Mixpanel. may want to disable if you're seeing 327 | issues with your crash reporting for either signals or exceptions 328 | @param automaticPushTracking whether or not to automatically track pushes sent from Mixpanel 329 | @param optOutTrackingByDefault whether or not to be opted out from tracking by default 330 | */ 331 | + (Mixpanel *)sharedInstanceWithToken:(NSString *)apiToken launchOptions:(nullable NSDictionary *)launchOptions trackCrashes:(BOOL)trackCrashes automaticPushTracking:(BOOL)automaticPushTracking optOutTrackingByDefault:(BOOL)optOutTrackingByDefault; 332 | 333 | /*! 334 | Returns a previously instantiated singleton instance of the API. 335 | 336 | The API must be initialized with sharedInstanceWithToken: or 337 | initWithToken:launchOptions:andFlushInterval before calling this class method. 338 | This method will return nil if there are no instances created. If there is more than 339 | one instace, it will return the first one that was created by using sharedInstanceWithToken: 340 | or initWithToken:launchOptions:andFlushInterval:. 341 | */ 342 | + (nullable Mixpanel *)sharedInstance; 343 | 344 | /*! 345 | Initializes an instance of the API with the given project token. This also sets 346 | it as a shared instance so you can use sharedInstance or 347 | sharedInstanceWithToken: to retrieve this object later. 348 | 349 | Creates and initializes a new API object. See also sharedInstanceWithToken:. 350 | 351 | @param apiToken your project token 352 | @param launchOptions optional app delegate launchOptions 353 | @param flushInterval interval to run background flushing 354 | @param trackCrashes whether or not to track crashes in Mixpanel. may want to disable if you're seeing 355 | issues with your crash reporting for either signals or exceptions 356 | */ 357 | - (instancetype)initWithToken:(NSString *)apiToken 358 | launchOptions:(nullable NSDictionary *)launchOptions 359 | flushInterval:(NSUInteger)flushInterval 360 | trackCrashes:(BOOL)trackCrashes; 361 | 362 | /*! 363 | Initializes an instance of the API with the given project token. This also sets 364 | it as a shared instance so you can use sharedInstance or 365 | sharedInstanceWithToken: to retrieve this object later. 366 | 367 | Creates and initializes a new API object. See also sharedInstanceWithToken:. 368 | 369 | @param apiToken your project token 370 | @param launchOptions optional app delegate launchOptions 371 | @param flushInterval interval to run background flushing 372 | @param trackCrashes whether or not to track crashes in Mixpanel. may want to disable if you're seeing 373 | issues with your crash reporting for either signals or exceptions 374 | @param automaticPushTracking whether or not to automatically track pushes sent from Mixpanel 375 | */ 376 | - (instancetype)initWithToken:(NSString *)apiToken 377 | launchOptions:(nullable NSDictionary *)launchOptions 378 | flushInterval:(NSUInteger)flushInterval 379 | trackCrashes:(BOOL)trackCrashes 380 | automaticPushTracking:(BOOL)automaticPushTracking; 381 | 382 | /*! 383 | Initializes an instance of the API with the given project token. 384 | 385 | Creates and initializes a new API object. See also sharedInstanceWithToken:. 386 | 387 | @param apiToken your project token 388 | @param launchOptions optional app delegate launchOptions 389 | @param flushInterval interval to run background flushing 390 | */ 391 | - (instancetype)initWithToken:(NSString *)apiToken 392 | launchOptions:(nullable NSDictionary *)launchOptions 393 | andFlushInterval:(NSUInteger)flushInterval; 394 | 395 | /*! 396 | Initializes an instance of the API with the given project token. 397 | 398 | Supports for the old initWithToken method format but really just passes 399 | launchOptions to the above method as nil. 400 | 401 | @param apiToken your project token 402 | @param flushInterval interval to run background flushing 403 | */ 404 | - (instancetype)initWithToken:(NSString *)apiToken andFlushInterval:(NSUInteger)flushInterval; 405 | 406 | /*! 407 | Sets the distinct ID of the current user. 408 | 409 | As of version 2.3.1, Mixpanel will choose a default distinct ID based on 410 | whether you are using the AdSupport.framework or not. 411 | 412 | If you are not using the AdSupport Framework (iAds), then we use the 413 | [UIDevice currentDevice].identifierForVendor (IFV) string as the 414 | default distinct ID. This ID will identify a user across all apps by the same 415 | vendor, but cannot be used to link the same user across apps from different 416 | vendors. 417 | 418 | If you are showing iAds in your application, you are allowed use the iOS ID 419 | for Advertising (IFA) to identify users. If you have this framework in your 420 | app, Mixpanel will use the IFA as the default distinct ID. If you have 421 | AdSupport installed but still don't want to use the IFA, you can define the 422 | MIXPANEL_NO_IFA preprocessor flag in your build settings, and 423 | Mixpanel will use the IFV as the default distinct ID. 424 | 425 | If we are unable to get an IFA or IFV, we will fall back to generating a 426 | random persistent UUID. 427 | 428 | For tracking events, you do not need to call identify: if you 429 | want to use the default. However, Mixpanel People always requires an 430 | explicit call to identify:. If calls are made to 431 | set:, increment or other MixpanelPeople 432 | methods prior to calling identify:, then they are queued up and 433 | flushed once identify: is called. 434 | 435 | If you'd like to use the default distinct ID for Mixpanel People as well 436 | (recommended), call identify: using the current distinct ID: 437 | [mixpanel identify:mixpanel.distinctId]. 438 | 439 | @param distinctId string that uniquely identifies the current user 440 | */ 441 | - (void)identify:(NSString *)distinctId; 442 | 443 | /*! 444 | Sets the distinct ID of the current user. With the option of only updating the 445 | distinct ID value and not the Mixpanel People distinct ID. 446 | 447 | This method is not intended to be used unless you wish to prevent updating the Mixpanel 448 | People distinct ID value by passing a value of NO to the usePeople param. This can be 449 | useful if the user wishes to prevent People updates from being sent until the identify 450 | method is called. 451 | 452 | @param distinctId string that uniquely identifies the current user 453 | @param usePeople bool controls whether or not to set the people distinctId to the event distinctId 454 | */ 455 | - (void)identify:(NSString *)distinctId usePeople:(BOOL)usePeople; 456 | 457 | /*! 458 | Add a group to this user's membership for a particular group key. 459 | The groupKey must be an NSString. The groupID should be a legal MixpanelType value. 460 | 461 | @param groupKey the group key 462 | @param groupID the group ID 463 | */ 464 | - (void)addGroup:(NSString *)groupKey groupID:(id)groupID; 465 | 466 | /*! 467 | Remove a group from this user's membership for a particular group key. 468 | The groupKey must be an NSString. The groupID should be a legal MixpanelType value. 469 | 470 | @param groupKey the group key 471 | @param groupID the group ID 472 | */ 473 | - (void)removeGroup:(NSString *)groupKey groupID:(id)groupID; 474 | 475 | /*! 476 | Set the group to which the user belongs. 477 | The groupKey must be an NSString. The groupID should be an array 478 | of MixpanelTypes. 479 | 480 | @param groupKey the group key 481 | @param groupIDs the group IDs 482 | */ 483 | - (void)setGroup:(NSString *)groupKey groupIDs:(NSArray> *)groupIDs; 484 | 485 | /*! 486 | Convenience method to set a single group ID for the current user. 487 | 488 | @param groupKey the group key 489 | @param groupID the group ID 490 | */ 491 | - (void)setGroup:(NSString *)groupKey groupID:(id)groupID; 492 | 493 | /*! 494 | Tracks an event with specific groups. 495 | 496 | Similar to track(), the data will also be sent to the specific group 497 | datasets. Group key/value pairs are upserted into the property map 498 | before tracking. 499 | The keys in groups must be NSString objects. values can be any legal 500 | MixpanelType objects. If the event is being timed, the timer will 501 | stop and be added as a property. 502 | 503 | @param event event name 504 | @param properties properties dictionary 505 | @param groups groups dictionary, which contains key-value pairs 506 | for this event 507 | */ 508 | - (void)trackWithGroups:(NSString *)event properties:(NSDictionary *)properties groups:(NSDictionary *)groups; 509 | 510 | /*! 511 | Get a MixpanelGroup identifier from groupKey and groupID. 512 | The groupKey must be an NSString. The groupID should be a legal MixpanelType value. 513 | 514 | @param groupKey the group key 515 | @param groupID the group ID 516 | */ 517 | - (MixpanelGroup *)getGroup:(NSString *)groupKey groupID:(id)groupID; 518 | 519 | /*! 520 | Tracks an event. 521 | 522 | @param event event name 523 | */ 524 | - (void)track:(NSString *)event; 525 | 526 | /*! 527 | Tracks an event with properties. 528 | 529 | Properties will allow you to segment your events in your Mixpanel reports. 530 | Property keys must be NSString objects and values must be 531 | NSString, NSNumber, NSNull, 532 | NSArray, NSDictionary, NSDate or 533 | NSURL objects. If the event is being timed, the timer will 534 | stop and be added as a property. 535 | 536 | @param event event name 537 | @param properties properties dictionary 538 | */ 539 | - (void)track:(NSString *)event properties:(nullable NSDictionary *)properties; 540 | 541 | /*! 542 | Registers super properties, overwriting ones that have already been set. 543 | 544 | Super properties, once registered, are automatically sent as properties for 545 | all event tracking calls. They save you having to maintain and add a common 546 | set of properties to your events. Property keys must be NSString 547 | objects and values must be NSString, NSNumber, 548 | NSNull, NSArray, NSDictionary, 549 | NSDate or NSURL objects. 550 | 551 | @param properties properties dictionary 552 | */ 553 | - (void)registerSuperProperties:(NSDictionary *)properties; 554 | 555 | /*! 556 | Registers super properties without overwriting ones that have already been 557 | set. 558 | 559 | Property keys must be NSString objects and values must be 560 | NSString, NSNumber, NSNull, 561 | NSArray, NSDictionary, NSDate or 562 | NSURL objects. 563 | 564 | @param properties properties dictionary 565 | */ 566 | - (void)registerSuperPropertiesOnce:(NSDictionary *)properties; 567 | 568 | /*! 569 | Registers super properties without overwriting ones that have already been set 570 | unless the existing value is equal to defaultValue. 571 | 572 | Property keys must be NSString objects and values must be 573 | NSString, NSNumber, NSNull, 574 | NSArray, NSDictionary, NSDate or 575 | NSURL objects. 576 | 577 | @param properties properties dictionary 578 | @param defaultValue overwrite existing properties that have this value 579 | */ 580 | - (void)registerSuperPropertiesOnce:(NSDictionary *)properties defaultValue:(nullable id)defaultValue; 581 | 582 | /*! 583 | Removes a previously registered super property. 584 | 585 | As an alternative to clearing all properties, unregistering specific super 586 | properties prevents them from being recorded on future events. This operation 587 | does not affect the value of other super properties. Any property name that is 588 | not registered is ignored. 589 | 590 | Note that after removing a super property, events will show the attribute as 591 | having the value undefined in Mixpanel until a new value is 592 | registered. 593 | 594 | @param propertyName array of property name strings to remove 595 | */ 596 | - (void)unregisterSuperProperty:(NSString *)propertyName; 597 | 598 | /*! 599 | Clears all currently set super properties. 600 | */ 601 | - (void)clearSuperProperties; 602 | 603 | /*! 604 | Returns the currently set super properties. 605 | */ 606 | - (NSDictionary *)currentSuperProperties; 607 | 608 | /*! 609 | Starts a timer that will be stopped and added as a property when a 610 | corresponding event is tracked. 611 | 612 | This method is intended to be used in advance of events that have 613 | a duration. For example, if a developer were to track an "Image Upload" event 614 | she might want to also know how long the upload took. Calling this method 615 | before the upload code would implicitly cause the track 616 | call to record its duration. 617 | 618 |
619 |  // begin timing the image upload
620 |  [mixpanel timeEvent:@"Image Upload"];
621 | 
622 |  // upload the image
623 |  [self uploadImageWithSuccessHandler:^{
624 | 
625 |     // track the event
626 |     [mixpanel track:@"Image Upload"];
627 |  }];
628 |  
629 | 630 | @param event a string, identical to the name of the event that will be tracked 631 | 632 | */ 633 | - (void)timeEvent:(NSString *)event; 634 | 635 | /*! 636 | Retrieves the time elapsed for the named event since timeEvent: was called. 637 | 638 | @param event the name of the event to be tracked that was passed to timeEvent: 639 | */ 640 | - (double)eventElapsedTime:(NSString *)event; 641 | 642 | /*! 643 | Clears all current event timers. 644 | */ 645 | - (void)clearTimedEvents; 646 | 647 | /*! 648 | Clears all stored properties and distinct IDs. Useful if your app's user logs out. 649 | */ 650 | - (void)reset; 651 | 652 | /*! 653 | Uploads queued data to the Mixpanel server. 654 | 655 | By default, queued data is flushed to the Mixpanel servers every minute (the 656 | default for flushInterval), and on background (since 657 | flushOnBackground is on by default). You only need to call this 658 | method manually if you want to force a flush at a particular moment. 659 | */ 660 | - (void)flush; 661 | 662 | /*! 663 | Calls flush, then optionally archives and calls a handler when finished. 664 | 665 | When calling flush manually, it is sometimes important to verify 666 | that the flush has finished before further action is taken. This is 667 | especially important when the app is in the background and could be suspended 668 | at any time if protocol is not followed. Delegate methods like 669 | application:didReceiveRemoteNotification:fetchCompletionHandler: 670 | are called when an app is brought to the background and require a handler to 671 | be called when it finishes. 672 | 673 | @param handler completion handler to be called after flush completes 674 | */ 675 | - (void)flushWithCompletion:(nullable void (^)(void))handler; 676 | 677 | /*! 678 | Writes current project info, including distinct ID, super properties and pending event 679 | and People record queues to disk. 680 | 681 | This state will be recovered when the app is launched again if the Mixpanel 682 | library is initialized with the same project token. You do not need to call 683 | this method. The library listens for app state changes and handles 684 | persisting data as needed. It can be useful in some special circumstances, 685 | though, for example, if you'd like to track app crashes from main.m. 686 | */ 687 | - (void)archive; 688 | 689 | /*! 690 | Creates a distinct_id alias from alias to original id. 691 | 692 | This method is used to map an identifier called an alias to the existing Mixpanel 693 | distinct id. This causes all events and people requests sent with the alias to be 694 | mapped back to the original distinct id. The recommended usage pattern is to call 695 | createAlias: and then identify: (with their new user ID) when they log in the next time. 696 | This will keep your signup funnels working correctly. 697 | 698 |
699 |  // This makes the current ID (an auto-generated GUID)
700 |  // and 'Alias' interchangeable distinct ids.
701 |  [mixpanel createAlias:@"Alias"
702 |     forDistinctID:mixpanel.distinctId];
703 | 
704 |  // You must call identify if you haven't already
705 |  // (e.g., when your app launches).
706 |  [mixpanel identify:mixpanel.distinctId];
707 | 
708 | 709 | @param alias the new distinct_id that should represent original 710 | @param distinctID the old distinct_id that alias will be mapped to 711 | */ 712 | - (void)createAlias:(NSString *)alias forDistinctID:(NSString *)distinctID; 713 | 714 | /*! 715 | Creates a distinct_id alias from alias to original id. 716 | 717 | This method is not intended to be used unless you wish to prevent updating the Mixpanel 718 | People distinct ID value by passing a value of NO to the usePeople param. This can be 719 | useful if the user wishes to prevent People updates from being sent until the identify 720 | method is called. 721 | 722 | @param alias the new distinct_id that should represent original 723 | @param distinctID the old distinct_id that alias will be mapped to 724 | @param usePeople bool controls whether or not to set the people distinctId to the event distinctId 725 | */ 726 | - (void)createAlias:(NSString *)alias forDistinctID:(NSString *)distinctID usePeople:(BOOL)usePeople; 727 | 728 | /*! 729 | Returns the Mixpanel library version number as a string, e.g. "3.2.3". 730 | */ 731 | - (NSString *)libVersion; 732 | 733 | /*! 734 | Opt out tracking. 735 | 736 | This method is used to opt out tracking. This causes all events and people request no longer 737 | to be sent back to the Mixpanel server. 738 | */ 739 | - (void)optOutTracking; 740 | 741 | /*! 742 | Opt in tracking. 743 | 744 | Use this method to opt in an already opted out user from tracking. People updates and track calls will be 745 | sent to Mixpanel after using this method. 746 | 747 | This method will internally track an opt in event to your project. If you want to identify the opt-in 748 | event and/or pass properties to the event, See also optInTrackingForDistinctId: and 749 | optInTrackingForDistinctId:withEventProperties:. 750 | */ 751 | - (void)optInTracking; 752 | 753 | /*! 754 | Opt in tracking. 755 | 756 | Use this method to opt in an already opted out user from tracking. People updates and track calls will be 757 | sent to Mixpanel after using this method. 758 | 759 | This method will internally track an opt in event to your project. If you want to pass properties to the event, see also 760 | optInTrackingForDistinctId:withEventProperties:. 761 | 762 | @param distinctID optional string to use as the distinct ID for events. This will call identify:. 763 | If you use people profiles make sure you manually call identify: after this method. 764 | */ 765 | - (void)optInTrackingForDistinctID:(nullable NSString *)distinctID; 766 | 767 | /*! 768 | Opt in tracking. 769 | 770 | Use this method to opt in an already opted out user from tracking. People updates and track calls will be 771 | sent to Mixpanel after using this method. 772 | 773 | This method will internally track an opt in event to your project.See also optInTracking or 774 | optInTrackingForDistinctId:. 775 | 776 | @param distinctID optional string to use as the distinct ID for events. This will call identify:. 777 | If you use people profiles make sure you manually call identify: after this method. 778 | @param properties optional properties dictionary that could be passed to add properties to the opt-in event that is sent to 779 | Mixpanel. 780 | */ 781 | - (void)optInTrackingForDistinctID:(nullable NSString *)distinctID withEventProperties:(nullable NSDictionary *)properties; 782 | 783 | /*! 784 | Returns YES if the current user has opted out tracking, NO if the current user has opted in tracking. 785 | */ 786 | - (BOOL)hasOptedOutTracking; 787 | 788 | /*! 789 | Returns the Mixpanel library version number as a string, e.g. "3.2.3". 790 | */ 791 | + (NSString *)libVersion; 792 | 793 | 794 | #if !MIXPANEL_NO_NOTIFICATION_AB_TEST_SUPPORT 795 | #pragma mark - Mixpanel Notifications 796 | 797 | /*! 798 | Shows the notification of the given id. 799 | 800 | You do not need to call this method on the main thread. 801 | 802 | @param ID notification id 803 | */ 804 | - (void)showNotificationWithID:(NSUInteger)ID; 805 | 806 | 807 | /*! 808 | Shows a notification with the given type if one is available. 809 | 810 | You do not need to call this method on the main thread. 811 | 812 | @param type The type of notification to show, either @"mini", or @"takeover" 813 | */ 814 | - (void)showNotificationWithType:(NSString *)type; 815 | 816 | /*! 817 | Shows a notification if one is available. 818 | 819 | You do not need to call this method on the main thread. 820 | */ 821 | - (void)showNotification; 822 | 823 | #pragma mark - Mixpanel A/B Testing 824 | 825 | /*! 826 | Join any experiments (A/B tests) that are available for the current user. 827 | 828 | Mixpanel will check for A/B tests automatically when your app enters 829 | the foreground. Call this method if you would like to to check for, 830 | and join, any experiments are newly available for the current user. 831 | 832 | You do not need to call this method on the main thread. 833 | */ 834 | - (void)joinExperiments; 835 | 836 | /*! 837 | Join any experiments (A/B tests) that are available for the current user. 838 | 839 | Same as joinExperiments but will fire the given callback after all experiments 840 | have been loaded and applied. 841 | 842 | @param experimentsLoadedCallback callback to be called after experiments 843 | joined and applied 844 | */ 845 | - (void)joinExperimentsWithCallback:(nullable void (^)(void))experimentsLoadedCallback; 846 | 847 | #endif // MIXPANEL_NO_NOTIFICATION_AB_TEST_SUPPORT 848 | 849 | #pragma mark - Deprecated 850 | /*! 851 | Current user's name in Mixpanel Streams. 852 | */ 853 | @property (nullable, atomic, copy) NSString *nameTag __deprecated; // Deprecated in v3.0.1 854 | 855 | @end 856 | 857 | /*! 858 | @protocol 859 | 860 | Delegate protocol for controlling the Mixpanel API's network behavior. 861 | 862 | Creating a delegate for the Mixpanel object is entirely optional. It is only 863 | necessary when you want full control over when data is uploaded to the server, 864 | beyond simply calling stop: and start: before and after a particular block of 865 | your code. 866 | */ 867 | 868 | @protocol MixpanelDelegate 869 | 870 | @optional 871 | /*! 872 | Asks the delegate if data should be uploaded to the server. 873 | 874 | Return YES to upload now, NO to defer until later. 875 | 876 | @param mixpanel Mixpanel API instance 877 | */ 878 | - (BOOL)mixpanelWillFlush:(Mixpanel *)mixpanel; 879 | 880 | @end 881 | 882 | NS_ASSUME_NONNULL_END 883 | -------------------------------------------------------------------------------- /RNMixpanel/MixpanelPeople.h: -------------------------------------------------------------------------------- 1 | // 2 | // MixpanelPeople.h 3 | // Mixpanel 4 | // 5 | // Created by Sam Green on 6/16/16. 6 | // Copyright © 2016 Mixpanel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /*! 14 | Mixpanel People API. 15 | 16 | Access to the Mixpanel People API, available as a property on the main 17 | Mixpanel API. 18 | 19 | You should not instantiate this object yourself. An instance of it will 20 | be available as a property of the main Mixpanel object. Calls to Mixpanel 21 | People methods will look like this: 22 | 23 |
 24 |  [mixpanel.people increment:@"App Opens" by:[NSNumber numberWithInt:1]];
 25 |  
26 | 27 | Please note that the core Mixpanel and 28 | MixpanelPeople classes share the identify: method. 29 | The Mixpanel identify: affects the 30 | distinct_id property of events sent by track: and 31 | track:properties: and determines which Mixpanel People user 32 | record will be updated by set:, increment: and other 33 | MixpanelPeople methods. 34 | 35 | If you are going to set your own distinct IDs for core Mixpanel event 36 | tracking, make sure to use the same distinct IDs when using Mixpanel 37 | People. 38 | */ 39 | @interface MixpanelPeople : NSObject 40 | /*! 41 | controls the $ignore_time property in any subsequent MixpanelPeople operation. 42 | 43 | If the $ignore_time property is present and true in your request, 44 | Mixpanel will not automatically update the "Last Seen" property of the profile. 45 | Otherwise, Mixpanel will add a "Last Seen" property associated with the 46 | current time for all $set, $append, and $add operations 47 | 48 | Defaults to NO. 49 | */ 50 | @property (atomic) BOOL ignoreTime; 51 | 52 | /*! 53 | Register the given device to receive push notifications. 54 | 55 | This will associate the device token with the current user in Mixpanel People, 56 | which will allow you to send push notifications to the user from the Mixpanel 57 | People web interface. You should call this method with the NSData 58 | token passed to 59 | application:didRegisterForRemoteNotificationsWithDeviceToken:. 60 | 61 | @param deviceToken device token as returned application:didRegisterForRemoteNotificationsWithDeviceToken: 62 | */ 63 | - (void)addPushDeviceToken:(NSData *)deviceToken; 64 | 65 | /*! 66 | Unregister the given device to receive push notifications. 67 | 68 | This will unset all of the push tokens saved to this people profile. This is useful 69 | in conjunction with a call to `reset`, or when a user is logging out. 70 | */ 71 | - (void)removeAllPushDeviceTokens; 72 | 73 | /*! 74 | Unregister a specific device token from the ability to receive push notifications. 75 | 76 | This will remove the provided push token saved to this people profile. This is useful 77 | in conjunction with a call to `reset`, or when a user is logging out. 78 | 79 | @param deviceToken device token to be unregistered 80 | */ 81 | - (void)removePushDeviceToken:(NSData *)deviceToken; 82 | 83 | /*! 84 | Set properties on the current user in Mixpanel People. 85 | 86 | The properties will be set on the current user. The keys must be NSString 87 | objects and the values should be NSString, NSNumber, NSArray, NSDate, or 88 | NSNull objects. We use an NSAssert to enforce this type requirement. In 89 | release mode, the assert is stripped out and we will silently convert 90 | incorrect types to strings using [NSString stringWithFormat:@"%@", value]. You 91 | can override the default the current project token and distinct ID by 92 | including the special properties: $token and $distinct_id. If the existing 93 | user record on the server already has a value for a given property, the old 94 | value is overwritten. Other existing properties will not be affected. 95 | 96 |
 97 |  // applies to both Mixpanel Engagement track: AND Mixpanel People set: and
 98 |  // increment: calls
 99 |  [mixpanel identify:distinctId];
100 |  
101 | 102 | @param properties properties dictionary 103 | 104 | */ 105 | - (void)set:(NSDictionary *)properties; 106 | 107 | /*! 108 | Convenience method for setting a single property in Mixpanel People. 109 | 110 | Property keys must be NSString objects and values must be 111 | NSString, NSNumber, NSNull, 112 | NSArray, NSDictionary, NSDate or 113 | NSURL objects. 114 | 115 | @param property property name 116 | @param object property value 117 | */ 118 | - (void)set:(NSString *)property to:(id)object; 119 | 120 | /*! 121 | Set properties on the current user in Mixpanel People, but don't overwrite if 122 | there is an existing value. 123 | 124 | This method is identical to set: except it will only set 125 | properties that are not already set. It is particularly useful for collecting 126 | data about the user's initial experience and source, as well as dates 127 | representing the first time something happened. 128 | 129 | @param properties properties dictionary 130 | 131 | */ 132 | - (void)setOnce:(NSDictionary *)properties; 133 | 134 | /*! 135 | Remove a list of properties and their values from the current user's profile 136 | in Mixpanel People. 137 | 138 | The properties array must ony contain NSString names of properties. For properties 139 | that don't exist there will be no effect. 140 | 141 | @param properties properties array 142 | 143 | */ 144 | - (void)unset:(NSArray *)properties; 145 | 146 | /*! 147 | Increment the given numeric properties by the given values. 148 | 149 | Property keys must be NSString names of numeric properties. A property is 150 | numeric if its current value is a number. If a property does not exist, it 151 | will be set to the increment amount. Property values must be NSNumber objects. 152 | 153 | @param properties properties dictionary 154 | */ 155 | - (void)increment:(NSDictionary *)properties; 156 | 157 | /*! 158 | Convenience method for incrementing a single numeric property by the specified 159 | amount. 160 | 161 | @param property property name 162 | @param amount amount to increment by 163 | */ 164 | - (void)increment:(NSString *)property by:(NSNumber *)amount; 165 | 166 | /*! 167 | Append values to list properties. 168 | 169 | Property keys must be NSString objects and values must be 170 | NSString, NSNumber, NSNull, 171 | NSArray, NSDictionary, NSDate or 172 | NSURL objects. 173 | 174 | @param properties mapping of list property names to values to append 175 | */ 176 | - (void)append:(NSDictionary *)properties; 177 | 178 | /*! 179 | Union list properties. 180 | 181 | Property keys must be NSString objects. 182 | 183 | @param properties mapping of list property names to lists to union 184 | */ 185 | - (void)union:(NSDictionary *)properties; 186 | 187 | /*! 188 | Remove list properties. 189 | 190 | Property keys must be NSString objects and values must be 191 | NSString, NSNumber, NSNull, 192 | NSArray, NSDictionary, NSDate or 193 | NSURL objects. 194 | 195 | @param properties mapping of list property names to values to remove 196 | */ 197 | - (void)remove:(NSDictionary *)properties; 198 | 199 | /*! 200 | Track money spent by the current user for revenue analytics. 201 | 202 | @param amount amount of revenue received 203 | */ 204 | - (void)trackCharge:(NSNumber *)amount; 205 | 206 | /*! 207 | Track money spent by the current user for revenue analytics and associate 208 | properties with the charge. 209 | 210 | Charge properties allow you segment on types of revenue. For instance, you 211 | could record a product ID with each charge so that you could segment on it in 212 | revenue analytics to see which products are generating the most revenue. 213 | */ 214 | - (void)trackCharge:(NSNumber *)amount withProperties:(nullable NSDictionary *)properties; 215 | 216 | 217 | /*! 218 | Delete current user's revenue history. 219 | */ 220 | - (void)clearCharges; 221 | 222 | /*! 223 | Delete current user's record from Mixpanel People. 224 | */ 225 | - (void)deleteUser; 226 | 227 | @end 228 | 229 | NS_ASSUME_NONNULL_END 230 | -------------------------------------------------------------------------------- /RNMixpanel/MixpanelType.h: -------------------------------------------------------------------------------- 1 | // 2 | // MixpanelType.h 3 | // Mixpanel 4 | // 5 | // Created by Weizhe Yuan on 9/6/18. 6 | // Copyright © 2018 Mixpanel. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol MixpanelType 12 | 13 | - (BOOL)equalToMixpanelType:(id)rhs; 14 | 15 | @end 16 | 17 | @interface NSString (MixpanelTypeCategory) 18 | 19 | @end 20 | 21 | @interface NSNumber (MixpanelTypeCategory) 22 | 23 | @end 24 | 25 | @interface NSArray (MixpanelTypeCategory) 26 | 27 | @end 28 | 29 | @interface NSDictionary (MixpanelTypeCategory) 30 | 31 | @end 32 | 33 | @interface NSDate (MixpanelTypeCategory) 34 | 35 | @end 36 | 37 | @interface NSURL (MixpanelTypeCategory) 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /RNMixpanel/RNMixpanel.h: -------------------------------------------------------------------------------- 1 | // 2 | // RNMixpanel.h 3 | // Dramsclub 4 | // 5 | // Created by Davide Scalzo on 08/11/2015. 6 | // Copyright © 2015 Facebook. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RNMixpanel : NSObject 12 | 13 | + (RNMixpanel *)sharedInstanceWithToken:(NSString *)apiToken 14 | optOutTrackingByDefault:(BOOL)optOutTrackingByDefault 15 | trackCrashes:(BOOL)trackCrashes 16 | automaticPushTracking:(BOOL)automaticPushTracking 17 | launchOptions:(NSDictionary *)launchOptions; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /RNMixpanel/RNMixpanel.m: -------------------------------------------------------------------------------- 1 | // 2 | // RNMixpanel.m 3 | // Dramsclub 4 | // 5 | // Created by Davide Scalzo on 08/11/2015. 6 | // Forked by Kevin Hylant on 5/3/2016. 7 | // Copyright © 2015 Facebook. All rights reserved. 8 | // 9 | 10 | #import "RNMixpanel.h" 11 | #import "Mixpanel.h" 12 | 13 | @interface Mixpanel (ReactNative) 14 | - (void)applicationDidBecomeActive:(NSNotification *)notification; 15 | @end 16 | 17 | @implementation RNMixpanel 18 | 19 | // to avoid having locks on every lookup, instances is kept in an immutable dictionary and reassigned when new instances are added 20 | NSDictionary *instances = nil; 21 | 22 | -(Mixpanel*) getInstance: (NSString *)name { 23 | Mixpanel* instance = [instances objectForKey:name]; // currently no error is thrown if an instance is missing 24 | return instance; 25 | } 26 | 27 | // Expose this module to the React Native bridge 28 | RCT_EXPORT_MODULE(RNMixpanel) 29 | 30 | // sharedInstanceWithToken 31 | RCT_EXPORT_METHOD(sharedInstanceWithToken:(NSString *)apiToken 32 | optOutTrackingByDefault:(BOOL)optOutTrackingByDefault 33 | trackCrashes:(BOOL)trackCrashes 34 | automaticPushTracking:(BOOL)automaticPushTracking 35 | launchOptions:(nullable NSDictionary *)launchOptions 36 | resolve:(RCTPromiseResolveBlock)resolve 37 | reject:(RCTPromiseRejectBlock)reject) { 38 | @synchronized(self) { 39 | if (instances != nil && [instances objectForKey:apiToken] != nil) { 40 | resolve(nil); 41 | return; 42 | } 43 | 44 | Mixpanel *instance = [Mixpanel sharedInstanceWithToken:apiToken 45 | launchOptions:launchOptions 46 | trackCrashes:trackCrashes 47 | automaticPushTracking:automaticPushTracking 48 | optOutTrackingByDefault:optOutTrackingByDefault]; 49 | 50 | NSString *serverURL = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"com.mixpanel.config.serverURL"]; 51 | if (serverURL != nil) { 52 | instance.serverURL = serverURL; 53 | } 54 | 55 | // copy instances and add the new instance. then reassign instances 56 | NSMutableDictionary *newInstances = [NSMutableDictionary dictionaryWithDictionary:instances]; 57 | [newInstances setObject:instance forKey:apiToken]; 58 | instances = [NSDictionary dictionaryWithDictionary:newInstances]; 59 | [instance applicationDidBecomeActive:nil]; 60 | resolve(nil); 61 | } 62 | } 63 | 64 | // setAppSessionProperties iOS 65 | RCT_EXPORT_METHOD(setAppSessionPropertiesIOS:(NSDictionary *)properties) { 66 | if ([properties objectForKey:@"minimumSessionDuration"]) { 67 | NSNumber *minimumSessionDuration = properties[@"minimumSessionDuration"]; 68 | long long int intValue = [minimumSessionDuration longLongValue]; 69 | [Mixpanel sharedInstance].minimumSessionDuration = intValue; 70 | } 71 | 72 | if ([properties objectForKey:@"maximumSessionDuration"]) { 73 | NSNumber *maximumSessionDuration = properties[@"maximumSessionDuration"]; 74 | long long int intValue = [maximumSessionDuration longLongValue]; 75 | [Mixpanel sharedInstance].maximumSessionDuration = intValue; 76 | } 77 | } 78 | 79 | // get distinct id 80 | RCT_EXPORT_METHOD(getDistinctId:(NSString *)apiToken 81 | resolve:(RCTPromiseResolveBlock)resolve 82 | reject:(RCTPromiseRejectBlock)reject) { 83 | resolve([self getInstance:apiToken].distinctId); 84 | } 85 | 86 | // get superProp 87 | RCT_EXPORT_METHOD(getSuperProperty: (NSString *)prop 88 | apiToken:(NSString *)apiToken 89 | resolve:(RCTPromiseResolveBlock)resolve 90 | reject:(RCTPromiseRejectBlock)reject) { 91 | NSDictionary *currSuperProps = [[self getInstance:apiToken] currentSuperProperties]; 92 | resolve([currSuperProps objectForKey:prop]); 93 | } 94 | 95 | // track 96 | RCT_EXPORT_METHOD(track:(NSString *)event 97 | apiToken:(NSString *)apiToken 98 | resolve:(RCTPromiseResolveBlock)resolve 99 | reject:(RCTPromiseRejectBlock)reject) { 100 | [[self getInstance:apiToken] track:event]; 101 | resolve(nil); 102 | } 103 | 104 | // disable ip address geolocalization 105 | RCT_EXPORT_METHOD(disableIpAddressGeolocalization:(NSString *)apiToken 106 | resolve:(RCTPromiseResolveBlock)resolve 107 | reject:(RCTPromiseRejectBlock)reject) { 108 | [self getInstance:apiToken].useIPAddressForGeoLocation = NO; 109 | resolve(nil); 110 | } 111 | 112 | // track with properties 113 | RCT_EXPORT_METHOD(trackWithProperties:(NSString *)event 114 | properties:(NSDictionary *)properties 115 | apiToken:(NSString *)apiToken 116 | resolve:(RCTPromiseResolveBlock)resolve 117 | reject:(RCTPromiseRejectBlock)reject 118 | ) { 119 | [[self getInstance:apiToken] track:event properties:properties]; 120 | resolve(nil); 121 | } 122 | 123 | // flush 124 | RCT_EXPORT_METHOD(flush:(NSString *)apiToken 125 | resolve:(RCTPromiseResolveBlock)resolve 126 | reject:(RCTPromiseRejectBlock)reject) { 127 | [[self getInstance:apiToken] flush]; 128 | resolve(nil);; 129 | } 130 | 131 | // create Alias 132 | RCT_EXPORT_METHOD(createAlias:(NSString *)alias 133 | oldDistinctID:(nullable NSString *)oldDistinctID 134 | apiToken:(NSString *)apiToken 135 | resolve:(RCTPromiseResolveBlock)resolve 136 | reject:(RCTPromiseRejectBlock)reject) { 137 | NSString *distinctId = oldDistinctID ? oldDistinctID : [self getInstance:apiToken].distinctId; 138 | [[self getInstance:apiToken] createAlias:alias forDistinctID:distinctId]; 139 | resolve(nil); 140 | } 141 | 142 | // identify 143 | RCT_EXPORT_METHOD(identify:(NSString *) uniqueId 144 | apiToken:(NSString *)apiToken 145 | resolve:(RCTPromiseResolveBlock)resolve 146 | reject:(RCTPromiseRejectBlock)reject) { 147 | [[self getInstance:apiToken] identify:uniqueId]; 148 | resolve(nil); 149 | } 150 | 151 | // add Group 152 | RCT_EXPORT_METHOD(addGroup:(NSString *)groupKey 153 | groupId:(NSString *)groupId 154 | apiToken:(NSString *)apiToken 155 | resolve:(RCTPromiseResolveBlock)resolve 156 | reject:(RCTPromiseRejectBlock)reject) { 157 | [[self getInstance:apiToken] addGroup:groupKey groupID:groupId]; 158 | resolve(nil); 159 | } 160 | 161 | // set Group 162 | RCT_EXPORT_METHOD(setGroup:(NSString *)groupKey 163 | groupId:(NSString *)groupId 164 | apiToken:(NSString *)apiToken 165 | resolve:(RCTPromiseResolveBlock)resolve 166 | reject:(RCTPromiseRejectBlock)reject) { 167 | [[self getInstance:apiToken] setGroup:groupKey groupID:groupId]; 168 | resolve(nil); 169 | } 170 | 171 | // Timing Events 172 | RCT_EXPORT_METHOD(timeEvent:(NSString *)event 173 | apiToken:(NSString *)apiToken 174 | resolve:(RCTPromiseResolveBlock)resolve 175 | reject:(RCTPromiseRejectBlock)reject) { 176 | [[self getInstance:apiToken] timeEvent:event]; 177 | resolve(nil); 178 | } 179 | 180 | // Register super properties 181 | RCT_EXPORT_METHOD(registerSuperProperties:(NSDictionary *)properties 182 | apiToken:(NSString *)apiToken 183 | resolve:(RCTPromiseResolveBlock)resolve 184 | reject:(RCTPromiseRejectBlock)reject) { 185 | [[self getInstance:apiToken] registerSuperProperties:properties]; 186 | resolve(nil); 187 | } 188 | 189 | // Register super properties Once 190 | RCT_EXPORT_METHOD(registerSuperPropertiesOnce:(NSDictionary *)properties 191 | apiToken:(NSString *)apiToken 192 | resolve:(RCTPromiseResolveBlock)resolve 193 | reject:(RCTPromiseRejectBlock)reject) { 194 | [[self getInstance:apiToken] registerSuperPropertiesOnce:properties]; 195 | resolve(nil); 196 | } 197 | 198 | RCT_EXPORT_METHOD(clearSuperProperties:(NSString *)apiToken 199 | resolve:(RCTPromiseResolveBlock)resolve 200 | reject:(RCTPromiseRejectBlock)reject) { 201 | [[self getInstance:apiToken] clearSuperProperties]; 202 | resolve(nil); 203 | } 204 | 205 | // Init push notification 206 | RCT_EXPORT_METHOD(initPushHandling:(NSString *) token 207 | apiToken:(NSString *)apiToken 208 | resolve:(RCTPromiseResolveBlock)resolve 209 | reject:(RCTPromiseRejectBlock)reject) { 210 | [self addPushDeviceToken:token apiToken:apiToken resolve:resolve reject:reject]; 211 | } 212 | 213 | // Set People Data 214 | RCT_EXPORT_METHOD(set:(NSDictionary *)properties 215 | apiToken:(NSString *)apiToken 216 | resolve:(RCTPromiseResolveBlock)resolve 217 | reject:(RCTPromiseRejectBlock)reject) { 218 | [[self getInstance:apiToken].people set:properties]; 219 | resolve(nil); 220 | } 221 | 222 | // Set People Data Once 223 | RCT_EXPORT_METHOD(setOnce:(NSDictionary *)properties 224 | apiToken:(NSString *)apiToken 225 | resolve:(RCTPromiseResolveBlock)resolve 226 | reject:(RCTPromiseRejectBlock)reject) { 227 | [[self getInstance:apiToken].people setOnce: properties]; 228 | resolve(nil); 229 | } 230 | 231 | // Remove Person's Push Token (iOS-only) 232 | RCT_EXPORT_METHOD(removePushDeviceToken:(NSString *)pushDeviceToken 233 | apiToken:(NSString *)apiToken 234 | resolve:(RCTPromiseResolveBlock)resolve 235 | reject:(RCTPromiseRejectBlock)reject) { 236 | 237 | NSMutableData *deviceToken = [[NSMutableData alloc] init]; 238 | unsigned char whole_byte; 239 | char byte_chars[3] = {'\0','\0','\0'}; 240 | int i; 241 | for (i=0; i < [pushDeviceToken length]/2; i++) { 242 | byte_chars[0] = [pushDeviceToken characterAtIndex:i*2]; 243 | byte_chars[1] = [pushDeviceToken characterAtIndex:i*2+1]; 244 | whole_byte = strtol(byte_chars, NULL, 16); 245 | [deviceToken appendBytes:&whole_byte length:1]; 246 | } 247 | 248 | [[self getInstance:apiToken].people removePushDeviceToken:deviceToken]; 249 | resolve(nil); 250 | } 251 | 252 | // Remove Person's Push Token (iOS-only) 253 | RCT_EXPORT_METHOD(removeAllPushDeviceTokens:(NSString *)apiToken 254 | resolve:(RCTPromiseResolveBlock)resolve 255 | reject:(RCTPromiseRejectBlock)reject) { 256 | [[self getInstance:apiToken].people removeAllPushDeviceTokens]; 257 | resolve(nil); 258 | } 259 | 260 | // track Revenue 261 | RCT_EXPORT_METHOD(trackCharge:(nonnull NSNumber *)charge 262 | apiToken:(NSString *)apiToken 263 | resolve:(RCTPromiseResolveBlock)resolve 264 | reject:(RCTPromiseRejectBlock)reject) { 265 | [[self getInstance:apiToken].people trackCharge:charge]; 266 | resolve(nil); 267 | } 268 | 269 | // track with properties 270 | RCT_EXPORT_METHOD(trackChargeWithProperties:(nonnull NSNumber *)charge 271 | properties:(NSDictionary *)properties 272 | apiToken:(NSString *)apiToken 273 | resolve:(RCTPromiseResolveBlock)resolve 274 | reject:(RCTPromiseRejectBlock)reject) { 275 | [[self getInstance:apiToken].people trackCharge:charge withProperties:properties]; 276 | resolve(nil); 277 | } 278 | 279 | // increment 280 | RCT_EXPORT_METHOD(increment:(NSString *)property 281 | count:(nonnull NSNumber *)count 282 | apiToken:(NSString *)apiToken 283 | resolve:(RCTPromiseResolveBlock)resolve 284 | reject:(RCTPromiseRejectBlock)reject) { 285 | [[self getInstance:apiToken].people increment:property by:count]; 286 | resolve(nil); 287 | } 288 | 289 | // Add Person's Push Token (iOS-only) 290 | RCT_EXPORT_METHOD(addPushDeviceToken:(NSString *)pushDeviceToken 291 | apiToken:(NSString *)apiToken 292 | resolve:(RCTPromiseResolveBlock)resolve 293 | reject:(RCTPromiseRejectBlock)reject) { 294 | NSMutableData *deviceToken = [[NSMutableData alloc] init]; 295 | unsigned char whole_byte; 296 | char byte_chars[3] = {'\0','\0','\0'}; 297 | int i; 298 | for (i=0; i < [pushDeviceToken length]/2; i++) { 299 | byte_chars[0] = [pushDeviceToken characterAtIndex:i*2]; 300 | byte_chars[1] = [pushDeviceToken characterAtIndex:i*2+1]; 301 | whole_byte = strtol(byte_chars, NULL, 16); 302 | [deviceToken appendBytes:&whole_byte length:1]; 303 | } 304 | [[self getInstance:apiToken].people addPushDeviceToken:deviceToken]; 305 | resolve(nil); 306 | } 307 | 308 | // People union 309 | RCT_EXPORT_METHOD(union:(NSString *)name 310 | properties:(NSArray *)properties 311 | apiToken:(NSString *)apiToken 312 | resolve:(RCTPromiseResolveBlock)resolve 313 | reject:(RCTPromiseRejectBlock)reject) { 314 | NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: properties, name, nil]; 315 | [[self getInstance:apiToken].people union:dict]; 316 | resolve(nil); 317 | } 318 | 319 | // People append 320 | RCT_EXPORT_METHOD(append:(NSString *)name 321 | properties:(NSArray *)properties 322 | apiToken:(NSString *)apiToken 323 | resolve:(RCTPromiseResolveBlock)resolve 324 | reject:(RCTPromiseRejectBlock)reject) { 325 | NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: properties, name, nil]; 326 | [[self getInstance:apiToken].people append:dict]; 327 | resolve(nil); 328 | } 329 | 330 | // reset 331 | RCT_EXPORT_METHOD(reset:(NSString *)apiToken 332 | resolve:(RCTPromiseResolveBlock)resolve 333 | reject:(RCTPromiseRejectBlock)reject) { 334 | [[self getInstance:apiToken] reset]; 335 | NSString *uuid = [[NSUUID UUID] UUIDString]; 336 | [[self getInstance:apiToken] identify:uuid]; 337 | resolve(nil); 338 | } 339 | 340 | // showNotification 341 | RCT_EXPORT_METHOD(showNotification:(NSString *)apiToken 342 | resolve:(RCTPromiseResolveBlock)resolve 343 | reject:(RCTPromiseRejectBlock)reject) { 344 | [[self getInstance:apiToken] showNotification]; 345 | resolve(nil); 346 | } 347 | 348 | // Opt in/out tracking 349 | RCT_EXPORT_METHOD(optOutTracking:(NSString *)apiToken 350 | resolve:(RCTPromiseResolveBlock)resolve 351 | reject:(RCTPromiseRejectBlock)reject) { 352 | [[self getInstance:apiToken] optOutTracking]; 353 | resolve(nil);; 354 | } 355 | 356 | RCT_EXPORT_METHOD(optInTracking:(NSString *)apiToken 357 | resolve:(RCTPromiseResolveBlock)resolve 358 | reject:(RCTPromiseRejectBlock)reject) { 359 | [[self getInstance:apiToken] optInTracking]; 360 | resolve(nil);; 361 | } 362 | 363 | @end 364 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion project.hasProperty('compileSdkVersion') ? project.compileSdkVersion : 23 5 | buildToolsVersion project.hasProperty('buildToolsVersion') ? project.buildToolsVersion : "23.0.1" 6 | 7 | defaultConfig { 8 | minSdkVersion project.hasProperty('minSdkVersion') ? project.minSdkVersion : 16 9 | targetSdkVersion project.hasProperty('targetSdkVersion') ? project.targetSdkVersion : 22 10 | versionCode 1 11 | versionName "1.0" 12 | ndk { 13 | abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64" 14 | } 15 | } 16 | } 17 | 18 | dependencies { 19 | implementation 'com.facebook.react:react-native:+' 20 | api "com.mixpanel.android:mixpanel-android:5.8.5" 21 | } 22 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/src/main/java/com/kevinejohn/RNMixpanel/RNMixpanel.java: -------------------------------------------------------------------------------- 1 | package com.kevinejohn.RNMixpanel; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.JavaScriptModule; 5 | import com.facebook.react.bridge.NativeModule; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.uimanager.ViewManager; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | /** 14 | * Created by KevinEJohn on 2/11/16. 15 | */ 16 | public class RNMixpanel implements ReactPackage { 17 | 18 | @Override 19 | public List createNativeModules( 20 | ReactApplicationContext reactContext) { 21 | List modules = new ArrayList<>(); 22 | 23 | modules.add(new RNMixpanelModule(reactContext)); 24 | 25 | return modules; 26 | } 27 | 28 | public List> createJSModules() { 29 | return Collections.emptyList(); 30 | } 31 | 32 | @Override 33 | public List createViewManagers( 34 | ReactApplicationContext reactContext) { 35 | return Collections.emptyList(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /android/src/main/java/com/kevinejohn/RNMixpanel/RNMixpanelModule.java: -------------------------------------------------------------------------------- 1 | package com.kevinejohn.RNMixpanel; 2 | 3 | import android.app.Activity; 4 | import com.facebook.react.bridge.LifecycleEventListener; 5 | import com.facebook.react.bridge.Promise; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 8 | import com.facebook.react.bridge.ReactMethod; 9 | import com.facebook.react.bridge.ReadableArray; 10 | import com.facebook.react.bridge.ReadableMap; 11 | import com.facebook.react.bridge.ReadableMapKeySetIterator; 12 | import com.facebook.react.bridge.ReadableType; 13 | import com.mixpanel.android.mpmetrics.MixpanelAPI; 14 | 15 | import org.json.JSONArray; 16 | import org.json.JSONException; 17 | import org.json.JSONObject; 18 | 19 | import java.util.Collections; 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | 23 | /** 24 | * Mixpanel React Native module. 25 | * Note that synchronized(instance) is used in methods because that's what MixpanelAPI.java recommends you do if you are keeping instances. 26 | */ 27 | public class RNMixpanelModule extends ReactContextBaseJavaModule implements LifecycleEventListener { 28 | 29 | private Map instances; 30 | 31 | public RNMixpanelModule(ReactApplicationContext reactContext) { 32 | super(reactContext); 33 | 34 | // Get lifecycle notifications to flush mixpanel on pause or destroy 35 | reactContext.addLifecycleEventListener(this); 36 | } 37 | 38 | /* 39 | Gets the mixpanel api instance for the given token. It must have been created in sharedInstanceWithToken first. 40 | */ 41 | private MixpanelAPI getInstance(final String apiToken) { 42 | if (instances == null) { 43 | return null; 44 | } 45 | return instances.get(apiToken); 46 | } 47 | 48 | @Override 49 | public String getName() { 50 | return "RNMixpanel"; 51 | } 52 | 53 | // Is there a better way to convert ReadableMap to JSONObject? 54 | // I only found this: 55 | // https://github.com/andpor/react-native-sqlite-storage/blob/master/src/android/src/main/java/org/pgsqlite/SQLitePluginConverter.java 56 | static JSONObject reactToJSON(ReadableMap readableMap) throws JSONException { 57 | JSONObject jsonObject = new JSONObject(); 58 | ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); 59 | while(iterator.hasNextKey()){ 60 | String key = iterator.nextKey(); 61 | ReadableType valueType = readableMap.getType(key); 62 | switch (valueType){ 63 | case Null: 64 | jsonObject.put(key,JSONObject.NULL); 65 | break; 66 | case Boolean: 67 | jsonObject.put(key, readableMap.getBoolean(key)); 68 | break; 69 | case Number: 70 | jsonObject.put(key, readableMap.getDouble(key)); 71 | break; 72 | case String: 73 | jsonObject.put(key, readableMap.getString(key)); 74 | break; 75 | case Map: 76 | jsonObject.put(key, reactToJSON(readableMap.getMap(key))); 77 | break; 78 | case Array: 79 | jsonObject.put(key, reactToJSON(readableMap.getArray(key))); 80 | break; 81 | } 82 | } 83 | 84 | return jsonObject; 85 | } 86 | 87 | static JSONArray reactToJSON(ReadableArray readableArray) throws JSONException { 88 | JSONArray jsonArray = new JSONArray(); 89 | for(int i=0; i < readableArray.size(); i++) { 90 | ReadableType valueType = readableArray.getType(i); 91 | switch (valueType){ 92 | case Null: 93 | jsonArray.put(JSONObject.NULL); 94 | break; 95 | case Boolean: 96 | jsonArray.put(readableArray.getBoolean(i)); 97 | break; 98 | case Number: 99 | jsonArray.put(readableArray.getDouble(i)); 100 | break; 101 | case String: 102 | jsonArray.put(readableArray.getString(i)); 103 | break; 104 | case Map: 105 | jsonArray.put(reactToJSON(readableArray.getMap(i))); 106 | break; 107 | case Array: 108 | jsonArray.put(reactToJSON(readableArray.getArray(i))); 109 | break; 110 | } 111 | } 112 | return jsonArray; 113 | } 114 | 115 | 116 | @Override 117 | public void onHostResume() { 118 | } 119 | 120 | @Override 121 | public void onHostPause() { 122 | if (instances != null) { 123 | for (MixpanelAPI instance : instances.values()) { 124 | instance.flush(); 125 | } 126 | } 127 | } 128 | 129 | @Override 130 | public void onHostDestroy() { 131 | if (instances != null) { 132 | for (MixpanelAPI instance : instances.values()) { 133 | instance.flush(); 134 | } 135 | } 136 | } 137 | 138 | @ReactMethod 139 | public void sharedInstanceWithToken(final String token, final Boolean optOutTracking, Promise promise) { 140 | synchronized (this) { 141 | // an instance can pre-exist when reloading javascript 142 | if (instances != null && instances.containsKey(token)) { 143 | promise.resolve(null); 144 | return; 145 | } 146 | final ReactApplicationContext reactApplicationContext = this.getReactApplicationContext(); 147 | if (reactApplicationContext == null) { 148 | promise.reject(new Throwable("no React application context")); 149 | return; 150 | } 151 | 152 | final MixpanelAPI instance = MixpanelAPI.getInstance(reactApplicationContext, 153 | token, 154 | optOutTracking); 155 | 156 | Map newInstances = new HashMap<>(); 157 | if (instances != null) { 158 | newInstances.putAll(instances); 159 | } 160 | newInstances.put(token, instance); 161 | instances = Collections.unmodifiableMap(newInstances); 162 | promise.resolve(null); 163 | } 164 | } 165 | 166 | @ReactMethod 167 | public void track(final String name, final String apiToken, Promise promise) { 168 | final MixpanelAPI instance = getInstance(apiToken); 169 | synchronized(instance) { 170 | instance.track(name); 171 | } 172 | promise.resolve(null); 173 | } 174 | 175 | @ReactMethod 176 | public void trackWithProperties(final String name, final ReadableMap properties, final String apiToken, Promise promise) { 177 | JSONObject obj = null; 178 | try { 179 | obj = RNMixpanelModule.reactToJSON(properties); 180 | } catch (JSONException e) { 181 | e.printStackTrace(); 182 | } 183 | 184 | final MixpanelAPI instance = getInstance(apiToken); 185 | synchronized(instance) { 186 | if (obj != null) { 187 | instance.track(name, obj); 188 | } 189 | } 190 | promise.resolve(null); 191 | } 192 | 193 | 194 | @ReactMethod 195 | public void createAlias(final String alias, final String oldDistinctID, final String apiToken, Promise promise) { 196 | final MixpanelAPI instance = getInstance(apiToken); 197 | synchronized(instance) { 198 | String distinctID = (oldDistinctID != null) ? oldDistinctID : instance.getDistinctId(); 199 | instance.alias(alias, distinctID); 200 | } 201 | promise.resolve(null); 202 | } 203 | 204 | @ReactMethod 205 | public void identify(final String user_id, final String apiToken, Promise promise) { 206 | final MixpanelAPI instance = getInstance(apiToken); 207 | synchronized(instance) { 208 | instance.identify(user_id); 209 | instance.getPeople().identify(user_id); 210 | } 211 | promise.resolve(null); 212 | } 213 | 214 | @ReactMethod 215 | public void addGroup(final String groupKey, final String groupId, final String apiToken, Promise promise) { 216 | final MixpanelAPI instance = getInstance(apiToken); 217 | synchronized(instance) { 218 | instance.addGroup(groupKey, groupId); 219 | } 220 | promise.resolve(null); 221 | } 222 | 223 | @ReactMethod 224 | public void setGroup(final String groupKey, final String groupId, final String apiToken, Promise promise) { 225 | final MixpanelAPI instance = getInstance(apiToken); 226 | synchronized(instance) { 227 | instance.setGroup(groupKey, groupId); 228 | } 229 | promise.resolve(null); 230 | } 231 | 232 | @ReactMethod 233 | public void timeEvent(final String event, final String apiToken, Promise promise) { 234 | final MixpanelAPI instance = getInstance(apiToken); 235 | synchronized(instance) { 236 | instance.timeEvent(event); 237 | } 238 | promise.resolve(null); 239 | } 240 | 241 | @ReactMethod 242 | public void registerSuperProperties(final ReadableMap properties, final String apiToken, Promise promise) { 243 | JSONObject obj = null; 244 | try { 245 | obj = RNMixpanelModule.reactToJSON(properties); 246 | } catch (JSONException e) { 247 | e.printStackTrace(); 248 | } 249 | final MixpanelAPI instance = getInstance(apiToken); 250 | synchronized(instance) { 251 | if (obj != null) { 252 | instance.registerSuperProperties(obj); 253 | } 254 | } 255 | promise.resolve(null); 256 | } 257 | 258 | @ReactMethod 259 | public void registerSuperPropertiesOnce(final ReadableMap properties, final String apiToken, Promise promise) { 260 | JSONObject obj = null; 261 | try { 262 | obj = RNMixpanelModule.reactToJSON(properties); 263 | } catch (JSONException e) { 264 | e.printStackTrace(); 265 | } 266 | final MixpanelAPI instance = getInstance(apiToken); 267 | synchronized(instance) { 268 | if (obj != null) { 269 | instance.registerSuperPropertiesOnce(obj); 270 | } 271 | } 272 | promise.resolve(null); 273 | } 274 | 275 | @ReactMethod 276 | public void clearSuperProperties(final String apiToken, Promise promise) { 277 | final MixpanelAPI instance = getInstance(apiToken); 278 | synchronized(instance) { 279 | instance.clearSuperProperties(); 280 | } 281 | promise.resolve(null); 282 | } 283 | 284 | @ReactMethod 285 | public void initPushHandling(final String token, final String apiToken, Promise promise) { 286 | // MixpanelAPI.initPushHandling is deprecated. 287 | // Mixpanel now uses Firebase Cloud Messaging. 288 | // initPushHandling will be removed in a future version. 289 | promise.resolve(null); 290 | } 291 | 292 | @ReactMethod 293 | public void set(final ReadableMap properties, final String apiToken, Promise promise) { 294 | JSONObject obj = null; 295 | try { 296 | obj = RNMixpanelModule.reactToJSON(properties); 297 | } catch (JSONException e) { 298 | e.printStackTrace(); 299 | } 300 | 301 | final MixpanelAPI instance = getInstance(apiToken); 302 | synchronized(instance) { 303 | if (obj != null) { 304 | instance.getPeople().set(obj); 305 | } 306 | } 307 | promise.resolve(null); 308 | } 309 | 310 | @ReactMethod 311 | public void setOnce(final ReadableMap properties, final String apiToken, Promise promise) { 312 | JSONObject obj = null; 313 | try { 314 | obj = RNMixpanelModule.reactToJSON(properties); 315 | } catch (JSONException e) { 316 | e.printStackTrace(); 317 | } 318 | final MixpanelAPI instance = getInstance(apiToken); 319 | synchronized(instance) { 320 | if (obj != null) { 321 | instance.getPeople().setOnce(obj); 322 | } 323 | } 324 | promise.resolve(null); 325 | } 326 | 327 | // Android only 328 | @ReactMethod 329 | public void getPushRegistrationId(final String apiToken, Promise promise) { 330 | final MixpanelAPI instance = getInstance(apiToken); 331 | if (instance == null) { 332 | promise.reject(new Throwable("no mixpanel instance available.")); 333 | return; 334 | } 335 | synchronized(instance) { 336 | promise.resolve(instance.getPeople().getPushRegistrationId()); 337 | } 338 | } 339 | 340 | // Android only 341 | @ReactMethod 342 | public void setPushRegistrationId(final String token, final String apiToken, Promise promise) { 343 | final MixpanelAPI instance = getInstance(apiToken); 344 | synchronized(instance) { 345 | instance.getPeople().setPushRegistrationId(token); 346 | } 347 | promise.resolve(null); 348 | } 349 | 350 | // Android only 351 | @ReactMethod 352 | public void clearPushRegistrationId(final String token, final String apiToken, Promise promise) { 353 | final MixpanelAPI instance = getInstance(apiToken); 354 | synchronized(instance) { 355 | if (token != null) { 356 | instance.getPeople().clearPushRegistrationId(token); 357 | } else { 358 | instance.getPeople().clearPushRegistrationId(); 359 | } 360 | } 361 | promise.resolve(null); 362 | } 363 | 364 | @ReactMethod 365 | public void trackCharge(final double charge, final String apiToken, Promise promise) { 366 | final MixpanelAPI instance = getInstance(apiToken); 367 | synchronized(instance) { 368 | instance.getPeople().trackCharge(charge, null); 369 | } 370 | promise.resolve(null); 371 | } 372 | 373 | @ReactMethod 374 | public void trackChargeWithProperties(final double charge, final ReadableMap properties, final String apiToken, Promise promise) { 375 | JSONObject obj = null; 376 | try { 377 | obj = RNMixpanelModule.reactToJSON(properties); 378 | } catch (JSONException e) { 379 | e.printStackTrace(); 380 | } 381 | final MixpanelAPI instance = getInstance(apiToken); 382 | synchronized(instance) { 383 | if (obj != null) { 384 | instance.getPeople().trackCharge(charge, obj); 385 | } 386 | } 387 | promise.resolve(null); 388 | } 389 | 390 | @ReactMethod 391 | public void increment(final String property, final double count, final String apiToken, Promise promise) { 392 | final MixpanelAPI instance = getInstance(apiToken); 393 | synchronized(instance) { 394 | instance.getPeople().increment(property, count); 395 | } 396 | promise.resolve(null); 397 | } 398 | 399 | @ReactMethod 400 | public void union(final String name, final ReadableArray properties, final String apiToken, Promise promise) { 401 | JSONArray obj = null; 402 | try { 403 | obj = RNMixpanelModule.reactToJSON(properties); 404 | } catch (JSONException e) { 405 | e.printStackTrace(); 406 | } 407 | final MixpanelAPI instance = getInstance(apiToken); 408 | synchronized(instance) { 409 | if (obj != null) { 410 | instance.getPeople().union(name, obj); 411 | } 412 | } 413 | promise.resolve(null); 414 | } 415 | 416 | @ReactMethod 417 | public void append(final String name, final ReadableArray properties, final String apiToken, Promise promise) { 418 | JSONArray obj = null; 419 | try { 420 | obj = RNMixpanelModule.reactToJSON(properties); 421 | } catch (JSONException e) { 422 | e.printStackTrace(); 423 | } 424 | final MixpanelAPI instance = getInstance(apiToken); 425 | synchronized(instance) { 426 | if (obj != null) { 427 | instance.getPeople().append(name, obj); 428 | } 429 | } 430 | promise.resolve(null); 431 | } 432 | 433 | @ReactMethod 434 | public void reset(final String apiToken, Promise promise) { 435 | final MixpanelAPI instance = getInstance(apiToken); 436 | synchronized(instance) { 437 | instance.reset(); 438 | instance.flush(); 439 | } 440 | promise.resolve(null); 441 | } 442 | 443 | @ReactMethod 444 | public void flush(final String apiToken, Promise promise) { 445 | final MixpanelAPI instance = getInstance(apiToken); 446 | synchronized(instance) { 447 | instance.flush(); 448 | } 449 | promise.resolve(null); 450 | } 451 | 452 | @ReactMethod 453 | public void getSuperProperty(final String property, final String apiToken, Promise promise) { 454 | final MixpanelAPI instance = getInstance(apiToken); 455 | synchronized(instance) { 456 | try { 457 | JSONObject currProps = instance.getSuperProperties(); 458 | promise.resolve(currProps.getString(property)); 459 | } catch (JSONException e) { 460 | promise.reject(e); 461 | } 462 | } 463 | } 464 | 465 | @ReactMethod 466 | public void getDistinctId(final String apiToken, Promise promise) { 467 | final MixpanelAPI instance = getInstance(apiToken); 468 | if (instance == null) { 469 | promise.reject(new Throwable("no mixpanel instance available.")); 470 | return; 471 | } 472 | synchronized(instance) { 473 | promise.resolve(instance.getDistinctId()); 474 | } 475 | } 476 | 477 | @ReactMethod 478 | public void showNotificationIfAvailable(final String apiToken, Promise promise) { 479 | final MixpanelAPI instance = getInstance(apiToken); 480 | if (instance == null) { 481 | promise.reject(new Throwable("no mixpanel instance available.")); 482 | return; 483 | } 484 | synchronized(instance) { 485 | Activity activity = this.getCurrentActivity(); 486 | MixpanelAPI.People people = instance.getPeople(); 487 | 488 | if(activity != null && people != null){ 489 | people.showNotificationIfAvailable(activity); 490 | } 491 | } 492 | promise.resolve(null); 493 | } 494 | 495 | @ReactMethod 496 | public void optOutTracking(final String apiToken, Promise promise) { 497 | final MixpanelAPI instance = getInstance(apiToken); 498 | if (instance == null) { 499 | promise.reject(new Throwable("no mixpanel instance available.")); 500 | return; 501 | } 502 | synchronized(instance) { 503 | instance.optOutTracking(); 504 | promise.resolve(null); 505 | } 506 | } 507 | 508 | @ReactMethod 509 | public void optInTracking(final String apiToken, Promise promise) { 510 | final MixpanelAPI instance = getInstance(apiToken); 511 | if (instance == null) { 512 | promise.reject(new Throwable("no mixpanel instance available.")); 513 | return; 514 | } 515 | synchronized(instance) { 516 | instance.optInTracking(); 517 | promise.resolve(null); 518 | } 519 | } 520 | } 521 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'react-native-mixpanel' { 2 | 3 | export class MixpanelInstance { 4 | constructor(apiToken?: string, optOutTrackingDefault?: boolean, trackCrashes?: boolean, automaticPushTracking?: boolean, launchOptions?: Object) 5 | 6 | initialize(): Promise 7 | getDistinctId(): Promise 8 | getSuperProperty(propertyName: string): Promise 9 | track(event: string, properties?: Object): Promise 10 | flush(): Promise 11 | disableIpAddressGeolocalization(): Promise 12 | alias(alias: string, oldDistinctID?: string): Promise 13 | identify(userId: string): Promise 14 | addGroup(groupKey: string, groupId: string): Promise 15 | setGroup(groupKey: string, groupId: string): Promise 16 | timeEvent(event: string): Promise 17 | registerSuperProperties(properties: Object): Promise 18 | registerSuperPropertiesOnce(properties: Object): Promise 19 | initPushHandling(token: string): Promise 20 | set(properties: Object): Promise 21 | setOnce(properties: Object): Promise 22 | trackCharge(charge: number): Promise 23 | trackChargeWithProperties(charge: number, properties: Object): Promise 24 | increment(property: string, by: number): Promise 25 | union(name: string, properties: any[]): Promise 26 | append(name: string, properties: any[]): Promise 27 | clearSuperProperties(): Promise 28 | reset(): Promise 29 | showInAppMessageIfAvailable(): Promise 30 | optInTracking(): Promise 31 | optOutTracking(): Promise 32 | 33 | // android only 34 | setPushRegistrationId(token: string): Promise 35 | clearPushRegistrationId(token?: string): Promise 36 | getPushRegistrationId(): Promise 37 | 38 | // iOS only 39 | setAppSessionPropertiesIOS(properties: Object): Promise; 40 | removePushDeviceToken(pushDeviceToken: string): Promise 41 | removeAllPushDeviceTokens(): Promise 42 | addPushDeviceToken(token: string): Promise 43 | } 44 | 45 | interface MixpanelAPI { 46 | sharedInstanceWithToken(apiToken: string, optOutTrackingDefault?: boolean, trackCrashes?: boolean, automaticPushTracking?: boolean, launchOptions?: Object): Promise; 47 | getDistinctId(callback: (id?: string) => void): void; 48 | getSuperProperty(propertyName: string, callback: (value: any) => void): void; 49 | track(event: string): void; 50 | trackWithProperties(event: string, properties: Object): void; 51 | flush(): void; 52 | disableIpAddressGeolocalization(): void; 53 | createAlias(alias: string, oldDistinctID?: string): void; 54 | identify(userId: string): void; 55 | addGroup(groupKey: string, groupId: string): void; 56 | setGroup(groupKey: string, groupId: string): void; 57 | timeEvent(event: string): void; 58 | registerSuperProperties(properties: Object): void; 59 | registerSuperPropertiesOnce(properties: Object): void; 60 | initPushHandling(token: string): void; 61 | set(properties: Object): void; 62 | setOnce(properties: Object): void; 63 | trackCharge(charge: number): void; 64 | trackChargeWithProperties(charge: number, properties: Object): void; 65 | increment(property: string, by: number): void; 66 | union(name: string, properties: any[]): void; 67 | append(name: string, properties: any[]): void; 68 | clearSuperProperties(): void; 69 | reset(): void; 70 | showInAppMessageIfAvailable(): void; 71 | optInTracking(): void 72 | optOutTracking(): void 73 | 74 | // android only 75 | setPushRegistrationId(token: string): void; 76 | clearPushRegistrationId(token?: string): void; 77 | getPushRegistrationId(callback: (token?: string) => void): void; 78 | 79 | // iOS only 80 | addPushDeviceToken(token: string): void; 81 | setAppSessionPropertiesIOS(properties: Object): Promise; 82 | removePushDeviceToken(pushDeviceToken: string): void; 83 | removeAllPushDeviceTokens(): void; 84 | } 85 | 86 | const mixpanelApi: MixpanelAPI; 87 | export default mixpanelApi; 88 | 89 | } 90 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 'use strict' 3 | import { NativeModules, Platform } from 'react-native' 4 | const { RNMixpanel } = NativeModules 5 | 6 | /* 7 | An error that is thrown or promise.rejected when a function is invoked before initialize() has completed. 8 | */ 9 | const NO_INSTANCE_ERROR = 'No mixpanel instance created yet. You must call sharedInstanceWithToken(token) or MixPanelInstance.initialize(token) before anything else and should wait for its promise to fulfill before others calls to avoid any internal native issue.' 10 | const uninitializedError: (string) => Error = (method: string) => new Error(`Mixpanel instance was not initialized yet. Please run initialize() and wait for its promise to resolve before calling ${method}(...)`) 11 | let defaultInstance:?MixpanelInstance = null 12 | 13 | /* 14 | A Mixpanel target. Normally there is only one of these. If you want to log to multiple Mixpanel projects, you can create a new instance of this class with a unique name. Then call initiaze and you can start logging. 15 | Most of the functions, like track and alias return a Promise. The functions will be called and normally you shouldn't have to care about awaiting them. 16 | However since React Native makes no guarantees about whether native methods are called in order, if you want to be 100% sure everything will work, like calling identify() before track(), you should await those promises to ensure everything is called properly. 17 | */ 18 | export class MixpanelInstance { 19 | apiToken: ?string 20 | optOutTrackingDefault: boolean 21 | trackCrashes: boolean 22 | automaticPushTracking: boolean 23 | launchOptions: object 24 | initialized: boolean 25 | 26 | constructor(apiToken: ?string, optOutTrackingDefault: ?boolean = false, trackCrashes: ?boolean = true, automaticPushTracking: ?boolean = true, launchOptions: ?Object = null) { 27 | this.apiToken = apiToken 28 | this.optOutTrackingDefault = optOutTrackingDefault 29 | this.trackCrashes = trackCrashes 30 | this.automaticPushTracking = automaticPushTracking 31 | this.launchOptions = launchOptions 32 | this.initialized = false 33 | } 34 | 35 | /* 36 | Initializes the instance in native land. Returns a promise that resolves when the instance has been created and is ready for use. 37 | */ 38 | initialize(): Promise { 39 | if (Platform.OS === 'ios'){ 40 | return RNMixpanel.sharedInstanceWithToken(this.apiToken, this.optOutTrackingDefault, this.trackCrashes, this.automaticPushTracking, this.launchOptions) 41 | .then(() => { 42 | this.initialized = true 43 | }) 44 | } else { 45 | return RNMixpanel.sharedInstanceWithToken(this.apiToken, this.optOutTrackingDefault) 46 | .then(() => { 47 | this.initialized = true 48 | }) 49 | } 50 | } 51 | 52 | /* 53 | Gets the unique identifier for the current user. Returns a promise that resolves with the value. 54 | */ 55 | getDistinctId(): Promise { 56 | if (!this.initialized) { 57 | return Promise.reject(new Error(uninitializedError('getDistinctId'))) 58 | } 59 | return RNMixpanel.getDistinctId(this.apiToken) 60 | } 61 | 62 | /* 63 | Retrieves current Firebase Cloud Messaging token. 64 | */ 65 | getPushRegistrationId(): Promise { 66 | if (!this.initialized) { 67 | return Promise.reject(new Error(uninitializedError('getPushRegistrationId'))) 68 | } 69 | if (!RNMixpanel.getPushRegistrationId) throw new Error('No native implementation for getPushRegistrationId. This is Android only.') 70 | return RNMixpanel.getPushRegistrationId(this.apiToken) 71 | } 72 | 73 | /* 74 | Gets the given super property. Returns a promise that resolves to the value. 75 | */ 76 | getSuperProperty(propertyName: string): Promise { 77 | if (!this.initialized) { 78 | return Promise.reject(new Error(uninitializedError('getSuperProperty'))) 79 | } 80 | return RNMixpanel.getSuperProperty(propertyName, this.apiToken) 81 | } 82 | 83 | /* 84 | Logs the event. 85 | */ 86 | track(event: string, properties?: Object): Promise { 87 | if (!this.initialized) throw new Error(uninitializedError('track')) 88 | if (properties) { 89 | return RNMixpanel.trackWithProperties(event, properties, this.apiToken) 90 | } else { 91 | return RNMixpanel.track(event, this.apiToken) 92 | } 93 | } 94 | 95 | flush(): Promise { 96 | if (!this.initialized) throw new Error(uninitializedError('flush')) 97 | return RNMixpanel.flush(this.apiToken) 98 | } 99 | 100 | disableIpAddressGeolocalization(): Promise { 101 | if (!this.initialized) throw new Error(uninitializedError('disableIpAddressGeolocalization')) 102 | return RNMixpanel.disableIpAddressGeolocalization(this.apiToken) 103 | } 104 | 105 | alias(alias: string, oldDistinctID?: string): Promise { 106 | if (!this.initialized) throw new Error(uninitializedError('createAlias')) 107 | 108 | return RNMixpanel.createAlias(alias, oldDistinctID, this.apiToken) 109 | } 110 | 111 | identify(userId: string): Promise { 112 | if (!this.initialized) throw new Error(uninitializedError('identify')) 113 | 114 | return RNMixpanel.identify(userId, this.apiToken) 115 | } 116 | 117 | addGroup(groupKey: string, groupId: string): Promise { 118 | if (!this.initialized) throw new Error(uninitializedError('addGroup')) 119 | 120 | return RNMixpanel.addGroup(groupKey, groupId, this.apiToken) 121 | } 122 | 123 | setGroup(groupKey: string, groupId: string): Promise { 124 | if (!this.initialized) throw new Error(uninitializedError('setGroup')) 125 | 126 | return RNMixpanel.setGroup(groupKey, groupId, this.apiToken) 127 | } 128 | 129 | timeEvent(event: string): Promise { 130 | if (!this.initialized) throw new Error(uninitializedError('timeEvent')) 131 | 132 | return RNMixpanel.timeEvent(event, this.apiToken) 133 | } 134 | 135 | registerSuperProperties(properties: Object): Promise { 136 | if (!this.initialized) throw new Error(uninitializedError('registerSuperProperties')) 137 | 138 | return RNMixpanel.registerSuperProperties(properties, this.apiToken) 139 | } 140 | 141 | registerSuperPropertiesOnce(properties: Object): Promise { 142 | if (!this.initialized) throw new Error(uninitializedError('registerSuperPropertiesOnce')) 143 | 144 | return RNMixpanel.registerSuperPropertiesOnce(properties, this.apiToken) 145 | } 146 | 147 | clearSuperProperties(): Promise { 148 | if (!this.initialized) throw new Error(uninitializedError('clearSuperProperties')) 149 | return RNMixpanel.clearSuperProperties(this.apiToken) 150 | } 151 | 152 | initPushHandling(token: string): Promise { 153 | if (!this.initialized) throw new Error(uninitializedError('initPushHandling')) 154 | 155 | return RNMixpanel.initPushHandling(token, this.apiToken) 156 | } 157 | 158 | set(properties: Object): Promise { 159 | if (!this.initialized) throw new Error(uninitializedError('set')) 160 | 161 | return RNMixpanel.set(properties, this.apiToken) 162 | } 163 | 164 | setOnce(properties: Object): Promise { 165 | if (!this.initialized) throw new Error(uninitializedError('setOnce')) 166 | 167 | return RNMixpanel.setOnce(properties, this.apiToken) 168 | } 169 | 170 | trackCharge(charge: number): Promise { 171 | if (!this.initialized) throw new Error(uninitializedError('trackCharge')) 172 | 173 | return RNMixpanel.trackCharge(charge, this.apiToken) 174 | } 175 | 176 | trackChargeWithProperties(charge: number, properties: Object): Promise { 177 | if (!this.initialized) throw new Error(uninitializedError('trackChargeWithProperties')) 178 | 179 | return RNMixpanel.trackChargeWithProperties(charge, properties, this.apiToken) 180 | } 181 | 182 | increment(property: string, by: number): Promise { 183 | if (!this.initialized) throw new Error(uninitializedError('increment')) 184 | 185 | return RNMixpanel.increment(property, by, this.apiToken) 186 | } 187 | 188 | union(name: string, properties: any[]): Promise { 189 | if (!this.initialized) throw new Error(uninitializedError('union')) 190 | 191 | return RNMixpanel.union(name, properties, this.apiToken) 192 | } 193 | 194 | append(name: string, properties: any[]): Promise { 195 | if (!this.initialized) throw new Error(uninitializedError('append')) 196 | 197 | return RNMixpanel.append(name, properties, this.apiToken) 198 | } 199 | 200 | removePushDeviceToken(pushDeviceToken: string): Promise { 201 | if (!this.initialized) throw new Error(uninitializedError('removePushDeviceToken')) 202 | 203 | return RNMixpanel.removePushDeviceToken(pushDeviceToken, this.apiToken) 204 | } 205 | 206 | setAppSessionPropertiesIOS(properties: Object): Promise { 207 | if (!this.initialized) throw new Error(uninitializedError('setAppSessionPropertiesIOS')) 208 | 209 | return RNMixpanel.setAppSessionPropertiesIOS(properties) 210 | } 211 | 212 | removeAllPushDeviceTokens(): Promise { 213 | if (!this.initialized) throw new Error(uninitializedError('removeAllPushDeviceTokens')) 214 | 215 | return RNMixpanel.removeAllPushDeviceTokens(this.apiToken) 216 | } 217 | 218 | addPushDeviceToken(token: string): Promise { 219 | if (!this.initialized) throw new Error(uninitializedError('addPushDeviceToken')) 220 | 221 | return RNMixpanel.addPushDeviceToken(token, this.apiToken) 222 | } 223 | 224 | // android only 225 | setPushRegistrationId(token: string): Promise { 226 | if (!this.initialized) throw new Error(uninitializedError('setPushRegistrationId')) 227 | 228 | if (!RNMixpanel.setPushRegistrationId) throw new Error('No native implementation for setPushRegistrationId. This is Android only.') 229 | return RNMixpanel.setPushRegistrationId(token, this.apiToken) 230 | } 231 | 232 | // android only 233 | clearPushRegistrationId(token?: string): Promise { 234 | if (!this.initialized) throw new Error(uninitializedError('clearPushRegistrationId')) 235 | 236 | if (!RNMixpanel.clearPushRegistrationId) throw new Error('No native implementation for setPusclearPushRegistrationIdhRegistrationId. This is Android only.') 237 | return RNMixpanel.clearPushRegistrationId(token, this.apiToken) 238 | } 239 | 240 | reset(): Promise { 241 | if (!this.initialized) throw new Error(uninitializedError('reset')) 242 | 243 | return RNMixpanel.reset(this.apiToken) 244 | } 245 | 246 | showInAppMessageIfAvailable(): Promise { 247 | if (!this.initialized) throw uninitializedError('showNotificationIfAvailable') 248 | 249 | if (Platform.OS === "android") { 250 | return RNMixpanel.showNotificationIfAvailable(this.apiToken) 251 | } else { 252 | return RNMixpanel.showNotification(this.apiToken) 253 | } 254 | } 255 | 256 | optInTracking(): Promise { 257 | if (!this.initialized) throw new Error(uninitializedError('optInTracking')) 258 | return RNMixpanel.optInTracking(this.apiToken) 259 | } 260 | 261 | optOutTracking(): Promise { 262 | if (!this.initialized) throw new Error(uninitializedError('optOutTracking')) 263 | return RNMixpanel.optOutTracking(this.apiToken) 264 | } 265 | } 266 | 267 | /* 268 | This is the original API and can still be used. However some may find it useful to use MixpanelInstance instead, like 269 | ``` 270 | const mixpanel = new MixpanelInstance(TOKEN) 271 | await mixpanel.initialize() 272 | mixpanel.track('my event') 273 | ``` 274 | */ 275 | export default { 276 | 277 | sharedInstanceWithToken(apiToken: string, optOutTrackingDefault: ?boolean = false, trackCrashes: ?boolean = true, automaticPushTracking: ?boolean = true, launchOptions: ?Object = null): Promise { 278 | const instance = new MixpanelInstance(apiToken, optOutTrackingDefault) 279 | if (!defaultInstance) defaultInstance = instance 280 | return instance.initialize() 281 | }, 282 | 283 | /* 284 | Gets the unique instance for a user. If you want to use promises, use the MixpanelInstace class API instead. 285 | */ 286 | getDistinctId(callback: (id: ?string) => void) { 287 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 288 | 289 | defaultInstance.getDistinctId() 290 | .then((id: string) => { 291 | callback(id) 292 | }) 293 | .catch((err) => { 294 | console.error('Error in mixpanel getDistinctId', err) 295 | callback(null) 296 | }) 297 | }, 298 | 299 | /* 300 | Retrieves current Firebase Cloud Messaging token. 301 | */ 302 | getPushRegistrationId(callback: (token: ?string) => void) { 303 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 304 | 305 | defaultInstance.getPushRegistrationId() 306 | .then((token: string) => { 307 | callback(token) 308 | }) 309 | .catch((err) => { 310 | console.error('Error in mixpanel getPushRegistrationId', err) 311 | callback(null) 312 | }) 313 | }, 314 | 315 | getSuperProperty(propertyName: string, callback: (value: mixed) => void) { 316 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 317 | 318 | defaultInstance.getSuperProperty(propertyName) 319 | .then((value: mixed) => { 320 | callback(value) 321 | }) 322 | .catch((err) => { 323 | console.error('Error in mixpanel getSuperProperty', err) 324 | callback(null) 325 | }) 326 | }, 327 | 328 | track(event: string) { 329 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 330 | 331 | defaultInstance.track(event) 332 | }, 333 | 334 | trackWithProperties(event: string, properties: Object) { 335 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 336 | 337 | defaultInstance.track(event, properties) 338 | }, 339 | 340 | flush() { 341 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 342 | 343 | defaultInstance.flush() 344 | }, 345 | 346 | disableIpAddressGeolocalization() { 347 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 348 | 349 | defaultInstance.disableIpAddressGeolocalization() 350 | }, 351 | 352 | createAlias(alias: string, oldDistinctID: ?string = null) { 353 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 354 | 355 | defaultInstance.alias(alias, oldDistinctID) 356 | }, 357 | 358 | identify(userId: string) { 359 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 360 | 361 | defaultInstance.identify(userId) 362 | }, 363 | 364 | addGroup(groupKey: string, groupId: string) { 365 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 366 | 367 | defaultInstance.addGroup(groupKey, groupId) 368 | }, 369 | 370 | setGroup(groupKey: string, groupId: string) { 371 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 372 | 373 | defaultInstance.setGroup(groupKey, groupId) 374 | }, 375 | 376 | timeEvent(event: string) { 377 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 378 | 379 | defaultInstance.timeEvent(event) 380 | }, 381 | 382 | registerSuperProperties(properties: Object) { 383 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 384 | 385 | defaultInstance.registerSuperProperties(properties) 386 | }, 387 | 388 | registerSuperPropertiesOnce(properties: Object) { 389 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 390 | 391 | defaultInstance.registerSuperPropertiesOnce(properties) 392 | }, 393 | 394 | clearSuperProperties() { 395 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 396 | 397 | defaultInstance.clearSuperProperties() 398 | }, 399 | 400 | initPushHandling(token: string) { 401 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 402 | 403 | defaultInstance.initPushHandling(token) 404 | }, 405 | 406 | set(properties: Object) { 407 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 408 | 409 | defaultInstance.set(properties) 410 | }, 411 | 412 | setOnce(properties: Object) { 413 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 414 | 415 | defaultInstance.setOnce(properties) 416 | }, 417 | 418 | removePushDeviceToken(pushDeviceToken: string) { 419 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 420 | 421 | defaultInstance.removePushDeviceToken(pushDeviceToken) 422 | }, 423 | 424 | setAppSessionPropertiesIOS(properties: Object) { 425 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 426 | 427 | defaultInstance.setAppSessionPropertiesIOS(properties) 428 | }, 429 | 430 | removeAllPushDeviceTokens() { 431 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 432 | 433 | defaultInstance.removeAllPushDeviceTokens() 434 | }, 435 | 436 | trackCharge(charge: number) { 437 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 438 | 439 | defaultInstance.trackCharge(charge) 440 | }, 441 | 442 | trackChargeWithProperties(charge: number, properties: Object) { 443 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 444 | 445 | defaultInstance.trackChargeWithProperties(charge, properties) 446 | }, 447 | 448 | increment(property: string, by: number) { 449 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 450 | 451 | defaultInstance.increment(property, by) 452 | }, 453 | 454 | union(name: string, properties: any[]) { 455 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 456 | 457 | defaultInstance.union(name, properties) 458 | }, 459 | 460 | append(name: string, properties: any[]) { 461 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 462 | 463 | defaultInstance.append(name, properties) 464 | }, 465 | 466 | addPushDeviceToken(token: string) { 467 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 468 | 469 | defaultInstance.addPushDeviceToken(token) 470 | }, 471 | 472 | // android only 473 | setPushRegistrationId(token: string) { 474 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 475 | 476 | defaultInstance.setPushRegistrationId(token) 477 | }, 478 | 479 | // android only 480 | clearPushRegistrationId(token?: string) { 481 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 482 | 483 | defaultInstance.clearPushRegistrationId(token) 484 | }, 485 | 486 | reset() { 487 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 488 | 489 | defaultInstance.reset() 490 | }, 491 | 492 | showInAppMessageIfAvailable() { 493 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 494 | defaultInstance.showInAppMessageIfAvailable() 495 | }, 496 | 497 | optInTracking() { 498 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 499 | 500 | defaultInstance.optInTracking() 501 | }, 502 | 503 | optOutTracking() { 504 | if (!defaultInstance) throw new Error(NO_INSTANCE_ERROR) 505 | 506 | defaultInstance.optOutTracking() 507 | }, 508 | } 509 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Davide Scalzo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-mixpanel", 3 | "version": "1.2.5", 4 | "description": "A React Native wrapper for Mixpanel tracking", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/davodesign84/react-native-mixpanel.git" 9 | }, 10 | "keywords": [ 11 | "react-native", 12 | "react-component", 13 | "tracking", 14 | "mixpanel" 15 | ], 16 | "author": "Davide Scalzo ", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/davodesign84/react-native-mixpanel/issues" 20 | }, 21 | "homepage": "https://github.com/davodesign84/react-native-mixpanel#readme" 22 | } 23 | -------------------------------------------------------------------------------- /react-native-mixpanel.podspec: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = package['name'] 7 | s.version = package['version'] 8 | s.summary = package['description'] 9 | s.license = package['license'] 10 | s.requires_arc = true 11 | s.authors = package['author'] 12 | s.homepage = package['homepage'] 13 | s.source = { :git => "https://github.com/davodesign84/react-native-mixpanel.git" } 14 | s.source_files = 'RNMixpanel/*' 15 | s.platform = :ios, "8.0" 16 | s.tvos.deployment_target = '10.0' 17 | s.dependency 'Mixpanel', '~> 3.6.0' 18 | s.dependency 'React' 19 | end 20 | --------------------------------------------------------------------------------