├── .editorconfig ├── .github └── lock.yml ├── .gitignore ├── LICENSE ├── README.md ├── android └── UniversalAnalyticsPlugin.java ├── browser └── UniversalAnalyticsProxy.js ├── ios ├── UniversalAnalyticsPlugin.h └── UniversalAnalyticsPlugin.m ├── lavaca └── AnalyticsService.js ├── package.json ├── plugin.xml ├── typescript └── analytics.d.ts ├── windows └── GoogleAnalyticsProxy.js ├── wp8 ├── PhoneHelpers.cs ├── PhoneNameResolver.cs ├── PlatformInfoProvider.WP.cs └── UniversalAnalytics.cs └── www └── analytics.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.github/lock.yml: -------------------------------------------------------------------------------- 1 | # Configuration for lock-threads - https://github.com/dessant/lock-threads 2 | 3 | # Number of days of inactivity before a closed issue or pull request is locked 4 | daysUntilLock: 365 5 | 6 | # Issues and pull requests with these labels will not be locked. Set to `[]` to disable 7 | exemptLabels: [] 8 | 9 | # Label to add before locking, such as `outdated`. Set to `false` to disable 10 | lockLabel: false 11 | 12 | # Comment to post before locking. Set to `false` to disable 13 | lockComment: > 14 | This thread has been automatically locked since there has not been 15 | any recent activity after it was closed. Please open a new issue for 16 | related bugs. 17 | 18 | # Limit to only `issues` or `pulls` 19 | # only: issues 20 | 21 | # Optionally, specify configuration settings just for `issues` or `pulls` 22 | # issues: 23 | # exemptLabels: 24 | # - help-wanted 25 | # lockLabel: outdated 26 | 27 | # pulls: 28 | # daysUntilLock: 30 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | 4 | node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 danwilson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | google-analytics-plugin 2 | ======================= 3 | [![npm](https://img.shields.io/npm/v/cordova-plugin-google-analytics.svg)](https://www.npmjs.com/package/cordova-plugin-google-analytics) 4 | [![npm](https://img.shields.io/npm/dt/cordova-plugin-google-analytics.svg?label=npm%20downloads)](https://www.npmjs.com/package/cordova-plugin-google-analytics) 5 | [![MIT license](http://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT) 6 | 7 | Cordova (PhoneGap) 3.0+ Plugin to connect to Google's native Universal Analytics SDK 8 | 9 | Prerequisites: 10 | * A Cordova 3.0+ project for iOS, Android, browser (PWA), Windows Phone 8 and/or Windows 10 (UWP) 11 | * A Mobile App property through the Google Analytics Admin Console 12 | * (Android) Google Play Services SDK installed via [Android SDK Manager](https://developer.android.com/sdk/installing/adding-packages.html) 13 | 14 | # Installing 15 | 16 | This plugin follows the Cordova 3.0+ plugin spec, so it can be installed through the Cordova CLI in your existing Cordova project: 17 | ```bash 18 | cordova plugin add https://github.com/danwilson/google-analytics-plugin.git 19 | ``` 20 | 21 | This plugin is also available on npm if you are using Cordova 5.0+: 22 | ```bash 23 | cordova plugin add cordova-plugin-google-analytics 24 | ``` 25 | 26 | ... OR the Cordova Plugin Registry if you are using a version of Cordova before 5.0 (while it remains open, as it is planning to shut down soon due to the move to npm): 27 | ```bash 28 | cordova plugin add com.danielcwilson.plugins.googleanalytics 29 | ``` 30 | 31 | *Important Note* If the latest versions (0.8.0+) of this plugin are not working for you with Android on Cordova 5.0+, please try the suggestions in [Issues 123](https://github.com/danwilson/google-analytics-plugin/issues/123#issuecomment-151145095). Google Play Services has been very confusing to integrate, but in recent months it has been simplified. This plugin uses the new simpler way (including it as a framework instead of bundling it which can conflict with other plugins bundling it), but if you previously installed this plugin some old files might still be lingering. 32 | 33 | The plugin.xml file will add the Google Analytics SDK files for Android, iOS, browser (PWA), Windows Phone 8 and/or Windows 10 (UWP). Follow [Google's steps](#sdk-files) if you need to update these later. Also make sure to review the Google Analytics [terms](http://www.google.com/analytics/terms/us.html) and [SDK Policy](https://developers.google.com/analytics/devguides/collection/protocol/policy) 34 | 35 | If you are not using the CLI, follow the steps in the section [Installing Without the CLI](#nocli) 36 | 37 | Windows Phone users have to manually add the [Google Analytics SDK for Windows 8 and Windows Phone](https://googleanalyticssdk.codeplex.com/) to your solution. To do this, just open your Cordova solution in Visual Studio, and add the [GoogleAnalyticsSDK package via NuGet](http://nuget.org/packages/GoogleAnalyticsSDK). This plugin requires v1.3.0 or higher. 38 | 39 | Windows 10 (UWP) users have to manually add the [Windows SDK for Google Analytics](https://github.com/dotnet/windows-sdk-for-google-analytics) to your solution. To do this, just open your Cordova solution in Visual Studio, and add the [UWP.SDKforGoogleAnalytics.Native package via NuGet](http://nuget.org/packages/UWP.SDKforGoogleAnalytics.Native). This plugin requires v1.5.2 or higher. 40 | 41 | # Configuring `play-services` Version 42 | 43 | Many other plugins require Google Play Services and/or Firebase libraries. This is a common source of Android build-failures, since the `play-services` library version must be aligned to the same version for **all** plugins. For example, when one plugin imports version `11.0.1` and another one imports `11.2.0`, a gradle build failure will occur. Use the `GMS_VERSION` to align the required play-services version with other plugins. 44 | 45 | ``` 46 | cordova plugin add cordova-plugin-google-analytics --variable GMS_VERSION=11.0.1 47 | ``` 48 | 49 | # Release note 50 | 51 | v1.0.0 -- api change from ```window.analytics``` to ```window.ga```, 'analytics' is deprecated since 1.0.0 and you should use the new api 'ga', 52 | because in the next release we are removing the analytics. 53 | 54 | v1.7.x -- since this version there are new parameters in some of the old methods like `startTrackerWithId('UA-XXXX-YY', 30)` 55 | and this is causing errors for those who are using the ionic 2(ionic-native) or ionic 1 (ngCordova); 56 | these wrapper interfaces don't have the new parameters at the time we did the changes; so please update you ionic framework to the lastest version. 57 | 58 | v1.7.11 -- since this version there is back compatibility with the new and old parameters in the method `startTrackerWithId('UA-XXXX-YY', 30)` to avoid loading issues reported. 59 | 60 | v1.8.4 -- fix conflicting versions of google play services due to multiple implementations. 61 | 62 | v1.9.0 -- since this version the windows platform is supported, also use Cocoa pods instead of static Framework for iOS. 63 | 64 | v1.9.1 -- fix use Cocoa pods instead of static Framework for iOS; it will add the dependenciesto the config.xml ``` ....``` 65 | 66 | v1.9.2 -- remove CampaignTrackingReceiver (deprecated on SDK31+), update analytics dependency to support SDK31+ 67 | 68 | v1.9.3 -- Added exported property on android: android:exported="true" 69 | 70 | # JavaScript Usage 71 | 72 | **All the following methods accept optional success and error callbacks after all other available parameters.** 73 | 74 | ```js 75 | //In your 'deviceready' handler, set up your Analytics tracker: 76 | window.ga.startTrackerWithId('UA-XXXX-YY', 30) 77 | //where UA-XXXX-YY is your Google Analytics Mobile App property and 30 is the dispatch period (optional) 78 | 79 | //To track a Screen (PageView): 80 | window.ga.trackView('Screen Title') 81 | 82 | //To track a Screen (PageView) w/ campaign details: 83 | window.ga.trackView('Screen Title', 'my-scheme://content/1111?utm_source=google&utm_campaign=my-campaign') 84 | 85 | //To track a Screen (PageView) and create a new session: 86 | window.ga.trackView('Screen Title', '', true) 87 | 88 | //To track an Event: 89 | window.ga.trackEvent('Category', 'Action', 'Label', Value)// Label and Value are optional, Value is numeric 90 | 91 | //To track an Event and create a new session: 92 | window.ga.trackEvent('Category', 'Action', 'Label', Value, true)// Label, Value and newSession are optional, Value is numeric, newSession is true/false 93 | 94 | //To track custom metrics: 95 | //(trackMetric doesn't actually send a hit, it's behaving more like the addCustomDimension() method. 96 | // The metric is afterwards added to every hit (view, event, error, etc...) sent, but the defined scope of the custom metric in analytics backend 97 | // (hit or product) will determine, at processing time, which hits are associated with the metric value.) 98 | window.ga.trackMetric(Key, Value) // Key and value are numeric type, Value is optional (omit value to unset metric) 99 | 100 | //To track an Exception: 101 | window.ga.trackException('Description', Fatal)//where Fatal is boolean 102 | 103 | //To track User Timing (App Speed): 104 | window.ga.trackTiming('Category', IntervalInMilliseconds, 'Variable', 'Label') // where IntervalInMilliseconds is numeric 105 | 106 | //To add a Transaction (Ecommerce) -- Deprecated on 1.9.0 will be removed on next minor version (1.10.0). 107 | window.ga.addTransaction('ID', 'Affiliation', Revenue, Tax, Shipping, 'Currency Code')// where Revenue, Tax, and Shipping are numeric 108 | 109 | //To add a Transaction Item (Ecommerce) -- Deprecated on 1.9.0 will be removed on next minor version (1.10.0). 110 | window.ga.addTransactionItem('ID', 'Name', 'SKU', 'Category', Price, Quantity, 'Currency Code')// where Price and Quantity are numeric 111 | 112 | //To add a Custom Dimension 113 | //(The dimension is afterwards added to every hit (view, event, error, etc...) sent, but the defined scope of the custom dimension in analytics backend 114 | // (hit or product) will determine, at processing time, which hits are associated with the dimension value.) 115 | window.ga.addCustomDimension(Key, 'Value', success, error) 116 | //Key should be integer index of the dimension i.e. send `1` instead of `dimension1` for the first custom dimension you are tracking. e.g. `window.ga.addCustomDimension(1, 'Value', success, error)` 117 | //Use empty string as value to unset custom dimension. 118 | 119 | //To set a UserId: 120 | window.ga.setUserId('my-user-id') 121 | 122 | //To set a specific app version: 123 | window.ga.setAppVersion('1.33.7') 124 | 125 | //To get a specific variable using this key list https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters: 126 | //for example to get campaign name: 127 | window.ga.getVar('cn', function(result){ console.log(result);}) 128 | 129 | //To set a specific variable using this key list https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters: 130 | //for example to set session control: 131 | window.ga.setVar('sc', 'end', function(result){ console.log(result);}) 132 | 133 | //To manually dispatch any data (this is not implemented in browser platform): 134 | window.ga.dispatch() 135 | 136 | //To set a anonymize Ip address: 137 | window.ga.setAnonymizeIp(true) 138 | 139 | //To set Opt-out: 140 | window.ga.setOptOut(true) 141 | 142 | //To enabling Advertising Features in Google Analytics allows you to take advantage of Remarketing, Demographics & Interests reports, and more: 143 | window.ga.setAllowIDFACollection(true) 144 | 145 | To enable verbose logging: 146 | window.ga.debugMode() 147 | // set's dry run mode on Android and Windows platform, so that all hits are only echoed back by the google analytics service and no actual hit is getting tracked! 148 | // **Android quirk**: verbose logging within javascript console is not supported. To see debug responses from analytics execute 149 | // `adb shell setprop log.tag.GAv4 DEBUG` and then `adb logcat -v time -s GAv4` to list messages 150 | // (see https://developers.google.com/android/reference/com/google/android/gms/analytics/Logger) 151 | 152 | //To enable/disable automatic reporting of uncaught exceptions 153 | window.ga.enableUncaughtExceptionReporting(Enable, success, error)// where Enable is boolean 154 | ``` 155 | 156 | # Example use ionic (Ionic Native) 157 | ```shell 158 | npm i --save @ionic-native/google-analytics 159 | ``` 160 | ```javascript 161 | import { GoogleAnalytics } from '@ionic-native/google-analytics'; 162 | import { Platform } from 'ionic-angular'; 163 | 164 | ... 165 | 166 | constructor(private ga: GoogleAnalytics, private platform: Platform) { } 167 | 168 | initializeApp() { 169 | this.platform.ready().then(() => { 170 | this.ga.startTrackerWithId('UA-00000000-0') 171 | .then(() => { 172 | console.log('Google analytics is ready now'); 173 | //the component is ready and you can call any method here 174 | this.ga.debugMode(); 175 | this.ga.setAllowIDFACollection(true); 176 | }) 177 | .catch(e => console.log('Error starting GoogleAnalytics', e)); 178 | }); 179 | } 180 | ``` 181 | 182 | **Working with multiple trackers in Ionic mobile app (Android & iOS)** 183 | ```javascript 184 | this.ga.startTrackerWithId('YOUR_TRACKER_ID_1') 185 | .then(() => { 186 | // Send a screen view to the first property. 187 | this.ga.trackView('First Tracker'); 188 | }) 189 | .catch(e => console.log('Error starting GoogleAnalytics', e)); 190 | this.ga2.startTrackerWithId('YOUR_TRACKER_ID_2') 191 | .then(() => { 192 | // Send another screen view to the second property. 193 | this.ga2.trackView('Second Tracker'); 194 | }) 195 | .catch(e => console.log('Error starting GoogleAnalytics', e)); 196 | ``` 197 | 198 | **Issue for using trackMetric in Ionic**: currently `@ionic-native/google-analytics` defines the typescript signature with `trackMetric(key: string, value?: any)`. 199 | So be aware to pass the metric index as a string formatted integer and a non empty string as a value, like `window.ga.trackMetric('1', 'Value', success, error)`! 200 | 201 | # Installing Without the CLI 202 | 203 | Copy the files manually into your project and add the following to your config.xml files: 204 | ```xml 205 | 206 | 207 | 208 | ``` 209 | ```xml 210 | 211 | 212 | 213 | ``` 214 | ```xml 215 | 216 | 217 | 218 | ``` 219 | 220 | You also will need to manually add the Google Analytics SDK files: 221 | * Download the Google Analytics SDK 3.0 for [iOS](https://developers.google.com/analytics/devguides/collection/ios/) and/or [Android](https://developers.google.com/analytics/devguides/collection/android/) 222 | * For iOS, add the downloaded Google Analytics SDK header files and libraries according to the [Getting Started](https://developers.google.com/analytics/devguides/collection/ios/v3) documentation 223 | * For Android, add `libGoogleAnalyticsServices.jar` to your Cordova Android project's `/libs` directory and build path 224 | * For Windows Phone, add the [GoogleAnalyticsSDK package via NuGet](http://nuget.org/packages/GoogleAnalyticsSDK) 225 | * For Windows 10 (UWP), add the [UWP.SDKforGoogleAnalytics.Native package via NuGet](http://nuget.org/packages/UWP.SDKforGoogleAnalytics.Native) 226 | 227 | # Integrating with Lavaca 228 | 229 | The `lavaca` directory includes a component that can be added to a Lavaca project. It offers a way to use the web `analytics.js` when the app is running in the browser and not packaged as Cordova. 230 | 231 | * Copy `AnalyticsService.js` to your Lavaca project (I create a directory under `js/app` called `data`). 232 | * In your config files (`local.json`, `staging.json`, `production.js`) create properties called `google_analytics_id` (for the Mobile App UA property) and `google_analytics_web_id` (for the Web UA property) and set the appropriate IDs or leave blank as needed. 233 | * In any file you want to track screen views or events, require AnalyticsService and use the methods provided. 234 | 235 | ```javascript 236 | var analyticsService = require('app/data/AnalyticsService'); 237 | 238 | analyticsService.trackView('Home'); 239 | ``` 240 | 241 | # Browser (PWA) 242 | 243 | For browser (PWA), people who want to use the plugin in a website that has already integrated google analytics needs 244 | to make sure that they remove the google analytics snippet from the head section of the page and change the global `ga` 245 | object name to something else. The plugin uses `nativeGa` instead. This can be changed by the following code. 246 | 247 | ```js 248 | // insert this in your head 249 | 252 | ``` 253 | 254 | The plugin will pick up the new name. 255 | 256 | # Windows 10 (UWP) 257 | 258 | The following plugin methods are (currently) not supported by the UWP.SDKforGoogleAnalytics.Native package: 259 | * `setAllowIDFACollection()` 260 | * `addTransaction()` 261 | * `addTransactionItem()` 262 | 263 | Unexpected behaviour may occur on the following methods: 264 | * `trackView()`: campaign details are currently not supported and therefore not tracked. 265 | * `trackMetric()`: there is currently a bug in version 1.5.2 of the [UWP.SDKforGoogleAnalytics.Native package via NuGet](http://nuget.org/packages/UWP.SDKforGoogleAnalytics.Native), 266 | that the wrong data specifier `cd` is taken for metrics, whereas `cm` should be the correct specifier. 267 | So as long as this bug is not fixed, trackMetrics will overwrite previous addCustomDimension with same index!! 268 | 269 | -------------------------------------------------------------------------------- /android/UniversalAnalyticsPlugin.java: -------------------------------------------------------------------------------- 1 | package com.danielcwilson.plugins.analytics; 2 | 3 | import com.google.android.gms.analytics.GoogleAnalytics; 4 | import com.google.android.gms.analytics.Logger.LogLevel; 5 | import com.google.android.gms.analytics.HitBuilders; 6 | import com.google.android.gms.analytics.Tracker; 7 | 8 | import org.apache.cordova.CordovaPlugin; 9 | import org.apache.cordova.CallbackContext; 10 | 11 | import org.json.JSONArray; 12 | import org.json.JSONException; 13 | 14 | import java.lang.reflect.InvocationTargetException; 15 | import java.lang.reflect.Method; 16 | import java.util.HashMap; 17 | import java.util.Map.Entry; 18 | 19 | public class UniversalAnalyticsPlugin extends CordovaPlugin { 20 | public static final String START_TRACKER = "startTrackerWithId"; 21 | public static final String TRACK_VIEW = "trackView"; 22 | public static final String TRACK_EVENT = "trackEvent"; 23 | public static final String TRACK_EXCEPTION = "trackException"; 24 | public static final String TRACK_TIMING = "trackTiming"; 25 | public static final String TRACK_METRIC = "trackMetric"; 26 | public static final String ADD_DIMENSION = "addCustomDimension"; 27 | public static final String ADD_TRANSACTION = "addTransaction"; 28 | public static final String ADD_TRANSACTION_ITEM = "addTransactionItem"; 29 | 30 | public static final String SET_ALLOW_IDFA_COLLECTION = "setAllowIDFACollection"; 31 | public static final String SET_USER_ID = "setUserId"; 32 | public static final String SET_ANONYMIZE_IP = "setAnonymizeIp"; 33 | public static final String SET_OPT_OUT = "setOptOut"; 34 | public static final String SET_APP_VERSION = "setAppVersion"; 35 | public static final String GET_VAR = "getVar"; 36 | public static final String SET_VAR = "setVar"; 37 | public static final String DISPATCH = "dispatch"; 38 | public static final String DEBUG_MODE = "debugMode"; 39 | public static final String ENABLE_UNCAUGHT_EXCEPTION_REPORTING = "enableUncaughtExceptionReporting"; 40 | 41 | public Boolean trackerStarted = false; 42 | public Boolean debugModeEnabled = false; 43 | public HashMap customDimensions = new HashMap(); 44 | public HashMap customMetrics = new HashMap(); 45 | 46 | public Tracker tracker; 47 | 48 | @Override 49 | public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { 50 | if (START_TRACKER.equals(action)) { 51 | String id = args.getString(0); 52 | int dispatchPeriod = args.length() > 1 ? args.getInt(1) : 30; 53 | this.startTracker(id, dispatchPeriod, callbackContext); 54 | return true; 55 | } else if (TRACK_VIEW.equals(action)) { 56 | int length = args.length(); 57 | String screen = args.getString(0); 58 | this.trackView(screen, length > 1 && !args.isNull(1) ? args.getString(1) : "", 59 | length > 2 && !args.isNull(2) ? args.getBoolean(2) : false, callbackContext); 60 | return true; 61 | } else if (TRACK_EVENT.equals(action)) { 62 | int length = args.length(); 63 | if (length > 0) { 64 | this.trackEvent(args.getString(0), length > 1 ? args.getString(1) : "", 65 | length > 2 ? args.getString(2) : "", length > 3 ? args.getLong(3) : 0, 66 | length > 4 ? args.getBoolean(4) : false, callbackContext); 67 | } 68 | return true; 69 | } else if (TRACK_EXCEPTION.equals(action)) { 70 | String description = args.getString(0); 71 | Boolean fatal = args.getBoolean(1); 72 | this.trackException(description, fatal, callbackContext); 73 | return true; 74 | } else if (TRACK_TIMING.equals(action)) { 75 | int length = args.length(); 76 | if (length > 0) { 77 | this.trackTiming(args.getString(0), length > 1 ? args.getLong(1) : 0, 78 | length > 2 ? args.getString(2) : "", length > 3 ? args.getString(3) : "", callbackContext); 79 | } 80 | return true; 81 | } else if (TRACK_METRIC.equals(action)) { 82 | int length = args.length(); 83 | if (length > 0) { 84 | this.trackMetric(args.getInt(0), length > 1 ? args.getString(1) : "", callbackContext); 85 | } 86 | return true; 87 | } else if (ADD_DIMENSION.equals(action)) { 88 | Integer key = args.getInt(0); 89 | String value = args.getString(1); 90 | this.addCustomDimension(key, value, callbackContext); 91 | return true; 92 | } else if (ADD_TRANSACTION.equals(action)) { 93 | int length = args.length(); 94 | if (length > 0) { 95 | this.addTransaction(args.getString(0), length > 1 ? args.getString(1) : "", 96 | length > 2 ? args.getDouble(2) : 0, length > 3 ? args.getDouble(3) : 0, 97 | length > 4 ? args.getDouble(4) : 0, length > 5 ? args.getString(5) : null, callbackContext); 98 | } 99 | return true; 100 | } else if (ADD_TRANSACTION_ITEM.equals(action)) { 101 | int length = args.length(); 102 | if (length > 0) { 103 | this.addTransactionItem(args.getString(0), length > 1 ? args.getString(1) : "", 104 | length > 2 ? args.getString(2) : "", length > 3 ? args.getString(3) : "", 105 | length > 4 ? args.getDouble(4) : 0, length > 5 ? args.getLong(5) : 0, 106 | length > 6 ? args.getString(6) : null, callbackContext); 107 | } 108 | return true; 109 | } else if (SET_ALLOW_IDFA_COLLECTION.equals(action)) { 110 | this.setAllowIDFACollection(args.getBoolean(0), callbackContext); 111 | } else if (SET_USER_ID.equals(action)) { 112 | String userId = args.getString(0); 113 | this.setUserId(userId, callbackContext); 114 | } else if (SET_ANONYMIZE_IP.equals(action)) { 115 | boolean anonymize = args.getBoolean(0); 116 | this.setAnonymizeIp(anonymize, callbackContext); 117 | } else if (SET_OPT_OUT.equals(action)) { 118 | boolean optout = args.getBoolean(0); 119 | this.setOptOut(optout, callbackContext); 120 | } else if (SET_APP_VERSION.equals(action)) { 121 | String version = args.getString(0); 122 | this.setAppVersion(version, callbackContext); 123 | } else if (GET_VAR.equals(action)) { 124 | String variable = args.getString(0); 125 | this.getVar(variable, callbackContext); 126 | } else if (SET_VAR.equals(action)) { 127 | String variable = args.getString(0); 128 | String value = args.getString(1); 129 | this.setVar(variable, value, callbackContext); 130 | return true; 131 | } else if (DISPATCH.equals(action)) { 132 | this.dispatch(callbackContext); 133 | return true; 134 | } else if (DEBUG_MODE.equals(action)) { 135 | this.debugMode(callbackContext); 136 | } else if (ENABLE_UNCAUGHT_EXCEPTION_REPORTING.equals(action)) { 137 | Boolean enable = args.getBoolean(0); 138 | this.enableUncaughtExceptionReporting(enable, callbackContext); 139 | } 140 | return false; 141 | } 142 | 143 | private void startTracker(String id, int dispatchPeriod, CallbackContext callbackContext) { 144 | if (null != id && id.length() > 0) { 145 | tracker = GoogleAnalytics.getInstance(this.cordova.getActivity()).newTracker(id); 146 | callbackContext.success("tracker started"); 147 | trackerStarted = true; 148 | GoogleAnalytics.getInstance(this.cordova.getActivity()).setLocalDispatchPeriod(dispatchPeriod); 149 | } else { 150 | callbackContext.error("tracker id is not valid"); 151 | } 152 | } 153 | 154 | private void addCustomDimension(Integer key, String value, CallbackContext callbackContext) { 155 | if (key <= 0) { 156 | callbackContext.error("Expected positive integer argument for key."); 157 | return; 158 | } 159 | 160 | if (null == value || value.length() == 0) { 161 | // unset dimension 162 | customDimensions.remove(key); 163 | callbackContext.success("custom dimension stopped"); 164 | } else { 165 | customDimensions.put(key, value); 166 | callbackContext.success("custom dimension started"); 167 | } 168 | } 169 | 170 | private void addCustomDimensionsAndMetricsToHitBuilder(T builder) { 171 | //unfortunately the base HitBuilders.HitBuilder class is not public, therefore have to use reflection to use 172 | //the common setCustomDimension (int index, String dimension) and setCustomMetrics (int index, Float metric) methods 173 | try { 174 | Method builderMethod = builder.getClass().getMethod("setCustomDimension", Integer.TYPE, String.class); 175 | 176 | for (Entry entry : customDimensions.entrySet()) { 177 | Integer key = entry.getKey(); 178 | String value = entry.getValue(); 179 | try { 180 | builderMethod.invoke(builder, (key), value); 181 | } catch (IllegalArgumentException e) { 182 | } catch (IllegalAccessException e) { 183 | } catch (InvocationTargetException e) { 184 | } 185 | } 186 | } catch (SecurityException e) { 187 | } catch (NoSuchMethodException e) { 188 | } 189 | 190 | try { 191 | Method builderMethod = builder.getClass().getMethod("setCustomMetric", Integer.TYPE, Float.TYPE); 192 | 193 | for (Entry entry : customMetrics.entrySet()) { 194 | Integer key = entry.getKey(); 195 | Float value = entry.getValue(); 196 | try { 197 | builderMethod.invoke(builder, (key), value); 198 | } catch (IllegalArgumentException e) { 199 | } catch (IllegalAccessException e) { 200 | } catch (InvocationTargetException e) { 201 | } 202 | } 203 | } catch (SecurityException e) { 204 | } catch (NoSuchMethodException e) { 205 | } 206 | } 207 | 208 | private void trackView(String screenname, String campaignUrl, boolean newSession, CallbackContext callbackContext) { 209 | if (!trackerStarted) { 210 | callbackContext.error("Tracker not started"); 211 | return; 212 | } 213 | 214 | if (null != screenname && screenname.length() > 0) { 215 | tracker.setScreenName(screenname); 216 | 217 | HitBuilders.ScreenViewBuilder hitBuilder = new HitBuilders.ScreenViewBuilder(); 218 | addCustomDimensionsAndMetricsToHitBuilder(hitBuilder); 219 | 220 | if (!campaignUrl.equals("")) { 221 | hitBuilder.setCampaignParamsFromUrl(campaignUrl); 222 | } 223 | 224 | if (!newSession) { 225 | tracker.send(hitBuilder.build()); 226 | } else { 227 | tracker.send(hitBuilder.setNewSession().build()); 228 | } 229 | 230 | callbackContext.success("Track Screen: " + screenname); 231 | } else { 232 | callbackContext.error("Expected one non-empty string argument."); 233 | } 234 | } 235 | 236 | private void trackEvent(String category, String action, String label, long value, boolean newSession, 237 | CallbackContext callbackContext) { 238 | if (!trackerStarted) { 239 | callbackContext.error("Tracker not started"); 240 | return; 241 | } 242 | 243 | if (null != category && category.length() > 0) { 244 | HitBuilders.EventBuilder hitBuilder = new HitBuilders.EventBuilder(); 245 | addCustomDimensionsAndMetricsToHitBuilder(hitBuilder); 246 | 247 | if (!newSession) { 248 | tracker.send( 249 | hitBuilder.setCategory(category).setAction(action).setLabel(label).setValue(value).build()); 250 | } else { 251 | tracker.send(hitBuilder.setCategory(category).setAction(action).setLabel(label).setValue(value) 252 | .setNewSession().build()); 253 | } 254 | 255 | callbackContext.success("Track Event: " + category); 256 | } else { 257 | callbackContext.error("Expected non-empty string arguments."); 258 | } 259 | } 260 | 261 | private void trackMetric(Integer key, String value, CallbackContext callbackContext) { 262 | if (key <= 0) { 263 | callbackContext.error("Expected positive integer argument for key."); 264 | return; 265 | } 266 | 267 | if (null == value || value.length() == 0) { 268 | // unset metric 269 | customMetrics.remove(key); 270 | callbackContext.success("custom metric stopped"); 271 | } else { 272 | Float floatValue; 273 | try { 274 | floatValue = Float.parseFloat(value); 275 | } catch (NumberFormatException e) { 276 | callbackContext.error("Expected string formatted number for value."); 277 | return; 278 | } 279 | 280 | customMetrics.put(key, floatValue); 281 | callbackContext.success("custom metric started"); 282 | } 283 | } 284 | 285 | private void trackException(String description, Boolean fatal, CallbackContext callbackContext) { 286 | if (!trackerStarted) { 287 | callbackContext.error("Tracker not started"); 288 | return; 289 | } 290 | 291 | if (null != description && description.length() > 0) { 292 | HitBuilders.ExceptionBuilder hitBuilder = new HitBuilders.ExceptionBuilder(); 293 | addCustomDimensionsAndMetricsToHitBuilder(hitBuilder); 294 | 295 | tracker.send(hitBuilder.setDescription(description).setFatal(fatal).build()); 296 | callbackContext.success("Track Exception: " + description); 297 | } else { 298 | callbackContext.error("Expected non-empty string arguments."); 299 | } 300 | } 301 | 302 | private void trackTiming(String category, long intervalInMilliseconds, String name, String label, 303 | CallbackContext callbackContext) { 304 | if (!trackerStarted) { 305 | callbackContext.error("Tracker not started"); 306 | return; 307 | } 308 | 309 | if (null != category && category.length() > 0) { 310 | HitBuilders.TimingBuilder hitBuilder = new HitBuilders.TimingBuilder(); 311 | addCustomDimensionsAndMetricsToHitBuilder(hitBuilder); 312 | 313 | tracker.send(hitBuilder.setCategory(category).setValue(intervalInMilliseconds).setVariable(name) 314 | .setLabel(label).build()); 315 | callbackContext.success("Track Timing: " + category); 316 | } else { 317 | callbackContext.error("Expected non-empty string arguments."); 318 | } 319 | } 320 | 321 | private void addTransaction(String id, String affiliation, double revenue, double tax, double shipping, 322 | String currencyCode, CallbackContext callbackContext) { 323 | if (!trackerStarted) { 324 | callbackContext.error("Tracker not started"); 325 | return; 326 | } 327 | 328 | if (null != id && id.length() > 0) { 329 | HitBuilders.TransactionBuilder hitBuilder = new HitBuilders.TransactionBuilder(); 330 | addCustomDimensionsAndMetricsToHitBuilder(hitBuilder); 331 | 332 | tracker.send(hitBuilder.setTransactionId(id).setAffiliation(affiliation).setRevenue(revenue).setTax(tax) 333 | .setShipping(shipping).setCurrencyCode(currencyCode).build()); //Deprecated 334 | callbackContext.success("Add Transaction: " + id); 335 | } else { 336 | callbackContext.error("Expected non-empty ID."); 337 | } 338 | } 339 | 340 | private void addTransactionItem(String id, String name, String sku, String category, double price, long quantity, 341 | String currencyCode, CallbackContext callbackContext) { 342 | if (!trackerStarted) { 343 | callbackContext.error("Tracker not started"); 344 | return; 345 | } 346 | 347 | if (null != id && id.length() > 0) { 348 | HitBuilders.ItemBuilder hitBuilder = new HitBuilders.ItemBuilder(); 349 | addCustomDimensionsAndMetricsToHitBuilder(hitBuilder); 350 | 351 | tracker.send(hitBuilder.setTransactionId(id).setName(name).setSku(sku).setCategory(category).setPrice(price) 352 | .setQuantity(quantity).setCurrencyCode(currencyCode).build()); //Deprecated 353 | callbackContext.success("Add Transaction Item: " + id); 354 | } else { 355 | callbackContext.error("Expected non-empty ID."); 356 | } 357 | } 358 | 359 | private void setAllowIDFACollection(Boolean enable, CallbackContext callbackContext) { 360 | if (!trackerStarted) { 361 | callbackContext.error("Tracker not started"); 362 | return; 363 | } 364 | 365 | tracker.enableAdvertisingIdCollection(enable); 366 | callbackContext.success("Enable Advertising Id Collection: " + enable); 367 | } 368 | 369 | private void setVar(String variable, String value, CallbackContext callbackContext) { 370 | if (!trackerStarted) { 371 | callbackContext.error("Tracker not started"); 372 | return; 373 | } 374 | 375 | tracker.set(variable, value); 376 | callbackContext.success("Set variable " + variable + "to " + value); 377 | } 378 | 379 | private void dispatch(CallbackContext callbackContext) { 380 | if (!trackerStarted) { 381 | callbackContext.error("Tracker not started"); 382 | return; 383 | } 384 | 385 | GoogleAnalytics.getInstance(this.cordova.getActivity()).dispatchLocalHits(); 386 | callbackContext.success("dispatch sent"); 387 | } 388 | 389 | private void getVar(String variable, CallbackContext callbackContext) { 390 | if (!trackerStarted) { 391 | callbackContext.error("Tracker not started"); 392 | return; 393 | } 394 | 395 | String result = tracker.get(variable); 396 | callbackContext.success(result); 397 | } 398 | 399 | private void debugMode(CallbackContext callbackContext) { 400 | // GAv4 Logger is deprecated! 401 | // GoogleAnalytics.getInstance(this.cordova.getActivity()).getLogger().setLogLevel(LogLevel.VERBOSE); 402 | 403 | // To enable verbose logging execute "adb shell setprop log.tag.GAv4 DEBUG" 404 | // and then "adb logcat -v time -s GAv4" to inspect log entries. 405 | GoogleAnalytics.getInstance(this.cordova.getActivity()).setDryRun(true); 406 | 407 | this.debugModeEnabled = true; 408 | callbackContext.success("debugMode enabled"); 409 | } 410 | 411 | private void setAnonymizeIp(boolean anonymize, CallbackContext callbackContext) { 412 | if (!trackerStarted) { 413 | callbackContext.error("Tracker not started"); 414 | return; 415 | } 416 | 417 | tracker.setAnonymizeIp(anonymize); 418 | callbackContext.success("Set AnonymizeIp " + anonymize); 419 | } 420 | 421 | private void setOptOut(boolean optout, CallbackContext callbackContext) { 422 | if (!trackerStarted) { 423 | callbackContext.error("Tracker not started"); 424 | return; 425 | } 426 | 427 | GoogleAnalytics.getInstance(this.cordova.getActivity()).setAppOptOut(optout); 428 | callbackContext.success("Set Opt-Out " + optout); 429 | } 430 | 431 | private void setUserId(String userId, CallbackContext callbackContext) { 432 | if (!trackerStarted) { 433 | callbackContext.error("Tracker not started"); 434 | return; 435 | } 436 | 437 | tracker.set("&uid", userId); 438 | callbackContext.success("Set user id" + userId); 439 | } 440 | 441 | private void setAppVersion(String version, CallbackContext callbackContext) { 442 | if (!trackerStarted) { 443 | callbackContext.error("Tracker not started"); 444 | return; 445 | } 446 | 447 | tracker.set("&av", version); 448 | callbackContext.success("Set app version: " + version); 449 | } 450 | 451 | private void enableUncaughtExceptionReporting(Boolean enable, CallbackContext callbackContext) { 452 | if (!trackerStarted) { 453 | callbackContext.error("Tracker not started"); 454 | return; 455 | } 456 | 457 | tracker.enableExceptionReporting(enable); 458 | callbackContext.success((enable ? "Enabled" : "Disabled") + " uncaught exception reporting"); 459 | } 460 | } 461 | -------------------------------------------------------------------------------- /browser/UniversalAnalyticsProxy.js: -------------------------------------------------------------------------------- 1 | function UniversalAnalyticsProxy() { 2 | this._isDebug = false; 3 | this._isEcommerceRequired = false; 4 | this._trackingId = null; 5 | 6 | var namespace = window.GoogleAnalyticsObject || 'nativeGa'; 7 | loadGoogleAnalytics.call(this, namespace); 8 | 9 | bindAll(this, [ 10 | '_ensureEcommerce', 11 | '_uncaughtExceptionHandler', 12 | 'addCustomDimension', 13 | 'addTransaction', 14 | 'addTransactionItem', 15 | 'debugMode', 16 | 'enableUncaughtExceptionReporting', 17 | 'setAllowIDFACollection', 18 | 'setAnonymizeIp', 19 | 'setAppVersion', 20 | 'setOptOut', 21 | 'setUserId', 22 | 'getVar', 23 | 'setVar', 24 | 'startTrackerWithId', 25 | 'trackEvent', 26 | 'trackException', 27 | 'trackMetric', 28 | 'trackTiming', 29 | 'trackView' 30 | ]); 31 | } 32 | 33 | UniversalAnalyticsProxy.prototype = { 34 | startTrackerWithId: wrap(function (trackingId) { 35 | this._trackingId = trackingId; 36 | 37 | this._ga('create', { 38 | trackingId: trackingId, 39 | cookieDomain: 'auto' 40 | }); 41 | this._ga('set', 'appName', document.title); 42 | }), 43 | 44 | setUserId: wrap(function (userId) { 45 | this._ga('set', 'userId', userId); 46 | }), 47 | 48 | setAnonymizeIp: wrap(function (anonymize) { 49 | this._ga('set', 'anonymizeIp', anonymize); 50 | }), 51 | 52 | setOptOut: wrap(function (optout) { 53 | if (!this._trackingId) { 54 | throw new Error('TrackingId not available'); 55 | } 56 | window['ga-disable-' + this._trackingId] = optout; 57 | }), 58 | 59 | setAppVersion: wrap(function (version) { 60 | this._ga('set', 'appVersion', version); 61 | }), 62 | 63 | setAllowIDFACollection: wrap(function (enable) { 64 | // Not supported by browser platofrm 65 | }), 66 | 67 | getVar: function (success, error, param) { 68 | this._ga(function(tracker){ 69 | success(tracker.get(param)); 70 | }); 71 | }, 72 | 73 | setVar: wrap(function(param, value){ 74 | this._ga('set', param, value); 75 | }), 76 | 77 | debugMode: wrap(function () { 78 | this._isDebug = true; 79 | }), 80 | 81 | addCustomDimension: wrap(function (key, value) { 82 | this._ga('set', 'dimension' + key, value); 83 | }), 84 | 85 | trackMetric: wrap(function (key, value) { 86 | this._ga('set', 'metric' + key, value); 87 | }), 88 | 89 | trackEvent: send(function (category, action, label, value, newSession) { 90 | return { 91 | hitType: 'event', 92 | eventCategory: category, 93 | eventAction: action, 94 | eventLabel: label, 95 | eventValue: value 96 | }; 97 | }), 98 | 99 | trackView: send(function (screen) { 100 | return { 101 | hitType: 'screenview', 102 | screenName: screen 103 | }; 104 | }), 105 | 106 | trackException: send(function (description, fatal) { 107 | return { 108 | hitType: 'exception', 109 | exDescription: description, 110 | exFatal: fatal 111 | }; 112 | }), 113 | 114 | trackTiming: send(function (category, intervalInMilliseconds, name, label) { 115 | return { 116 | hitType: 'timing', 117 | timingCategory: category, 118 | timingVar: name, 119 | timingValue: intervalInMilliseconds, 120 | timingLabel: label 121 | }; 122 | }), 123 | 124 | addTransaction: wrap(function (transactionId, affiliation, revenue, tax, shipping, currencyCode) { 125 | this._ensureEcommerce(); 126 | this._ga('ecommerce:addTransaction', { 127 | id: transactionId, 128 | affiliation: affiliation, 129 | revenue: String(revenue), 130 | shipping: String(shipping), 131 | tax: String(tax), 132 | currency: currencyCode 133 | }); 134 | }), 135 | 136 | addTransactionItem: wrap(function (transactionId, name, sku, category, price, quantity, currencyCode) { 137 | this._ensureEcommerce(); 138 | this._ga('ecommerce:addItem', { 139 | id: transactionId, 140 | name: name, 141 | sku: sku, 142 | category: category, 143 | price: String(price), 144 | quantity: String(quantity), 145 | currency: currencyCode 146 | }); 147 | }), 148 | 149 | enableUncaughtExceptionReporting: wrap(function (enable) { 150 | if (enable) { 151 | window.addEventListener('error', this._uncaughtExceptionHandler); 152 | } else { 153 | window.removeEventListener('error', this._uncaughtExceptionHandler); 154 | } 155 | }), 156 | 157 | _ga: function () { 158 | var args = Array.prototype.slice.call(arguments); 159 | if (this._isDebug) { 160 | console.debug('UniversalAnalyticsProxy', args); 161 | } 162 | this._nativeGa.apply(this._nativeGa, args); 163 | }, 164 | 165 | _uncaughtExceptionHandler: function (err) { 166 | this._ga('send', { 167 | hitType: 'exception', 168 | exDescription: err.message, 169 | exFatal: true 170 | }); 171 | }, 172 | 173 | _ensureEcommerce: function() { 174 | if (this._isEcommerceRequired) return; 175 | this._ga('require', 'ecommerce'); 176 | this._isEcommerceRequired = true; 177 | } 178 | }; 179 | 180 | function send(fn) { 181 | return function (success, error, args) { 182 | var command = fn.apply(this, args); 183 | var timeout = setTimeout(function () { 184 | error(new Error('send timeout')); 185 | }, 3000); 186 | 187 | command.hitCallback = function hitCallback(result) { 188 | clearTimeout(timeout); 189 | success(result); 190 | }; 191 | 192 | try { 193 | this._ga('send', command); 194 | } catch (err) { 195 | clearTimeout(timeout); 196 | defer(error, err); 197 | } 198 | }; 199 | } 200 | 201 | function bindAll(that, names) { 202 | names.forEach(function(name) { 203 | if (typeof that[name] === 'function') { 204 | that[name] = that[name].bind(that); 205 | } 206 | }); 207 | } 208 | 209 | /** 210 | * Proceed to the asynchronous loading of Google's analytics.js. 211 | * Initialize `this._nativeGa` once the script is loaded, using 212 | * the `onload` callback of the `script` DOM node. 213 | * 214 | * @param {string} name Reference (global namespace) of the GA object. 215 | */ 216 | function loadGoogleAnalytics(name) { 217 | window.GoogleAnalyticsObject = name; 218 | 219 | window[name] = window[name] || function () { 220 | (window[name].q = window[name].q || []).push(arguments); 221 | }; 222 | window[name].l = 1 * new Date(); 223 | this._nativeGa = window[name]; 224 | 225 | var script = document.createElement('script'); 226 | var scripts = document.getElementsByTagName('script')[0]; 227 | script.src = 'https://www.google-analytics.com/analytics.js'; 228 | script.async = 1; 229 | scripts.parentNode.insertBefore(script, scripts); 230 | 231 | // analytics.js creates a new object once initialized, update our reference 232 | script.onload = (function() { this._nativeGa = window[name]; }).bind(this); 233 | } 234 | 235 | function wrap(fn) { 236 | return function (success, error, args) { 237 | try { 238 | fn.apply(this, args); 239 | setTimeout(success, 0); 240 | } catch (err) { 241 | defer(error, err); 242 | } 243 | }; 244 | } 245 | 246 | function defer(fn) { 247 | var args = Array.prototype.slice.call(arguments, 1); 248 | setTimeout(function () { 249 | fn.apply(null, args); 250 | }, 0); 251 | } 252 | 253 | require('cordova/exec/proxy').add('UniversalAnalytics', new UniversalAnalyticsProxy()); 254 | -------------------------------------------------------------------------------- /ios/UniversalAnalyticsPlugin.h: -------------------------------------------------------------------------------- 1 | //UniversalAnalyticsPlugin.h 2 | //Created by Daniel Wilson 2013-09-19 3 | 4 | #import 5 | #import 6 | #import 7 | #import 8 | #import 9 | 10 | @interface UniversalAnalyticsPlugin : CDVPlugin { 11 | bool _trackerStarted; 12 | bool _debugMode; 13 | NSMutableDictionary *_customDimensions; 14 | } 15 | 16 | - (void) startTrackerWithId: (CDVInvokedUrlCommand*)command; 17 | - (void) setAllowIDFACollection: (CDVInvokedUrlCommand*) command; 18 | - (void) setUserId: (CDVInvokedUrlCommand*)command; 19 | - (void) setAppVersion: (CDVInvokedUrlCommand*)command; 20 | - (void) getVar: (CDVInvokedUrlCommand*)command; 21 | - (void) setVar: (CDVInvokedUrlCommand*)command; 22 | - (void) dispatch: (CDVInvokedUrlCommand*)command; 23 | - (void) debugMode: (CDVInvokedUrlCommand*)command; 24 | - (void) setOptOut: (CDVInvokedUrlCommand*)command; 25 | - (void) enableUncaughtExceptionReporting: (CDVInvokedUrlCommand*)command; 26 | - (void) addCustomDimension: (CDVInvokedUrlCommand*)command; 27 | - (void) trackEvent: (CDVInvokedUrlCommand*)command; 28 | - (void) trackMetric: (CDVInvokedUrlCommand*)command; 29 | - (void) trackTiming: (CDVInvokedUrlCommand*)command; 30 | - (void) trackView: (CDVInvokedUrlCommand*)command; 31 | - (void) trackException: (CDVInvokedUrlCommand*)command; 32 | - (void) addTransaction: (CDVInvokedUrlCommand*)command; 33 | - (void) addTransactionItem: (CDVInvokedUrlCommand*)command; 34 | 35 | @end 36 | 37 | -------------------------------------------------------------------------------- /ios/UniversalAnalyticsPlugin.m: -------------------------------------------------------------------------------- 1 | //UniversalAnalyticsPlugin.m 2 | //Created by Daniel Wilson 2013-09-19 3 | 4 | #import "UniversalAnalyticsPlugin.h" 5 | #import 6 | #import 7 | #import 8 | 9 | @implementation UniversalAnalyticsPlugin 10 | 11 | - (void) pluginInitialize 12 | { 13 | _debugMode = false; 14 | _trackerStarted = false; 15 | _customDimensions = nil; 16 | } 17 | 18 | - (void) startTrackerWithId: (CDVInvokedUrlCommand*)command 19 | { 20 | [self.commandDelegate runInBackground:^{ 21 | CDVPluginResult* pluginResult = nil; 22 | NSString* accountId = [command.arguments objectAtIndex:0]; 23 | NSNumber* dispatchPeriod = [command.arguments objectAtIndex:1]; 24 | 25 | if ([dispatchPeriod isKindOfClass:[NSNumber class]]) 26 | [GAI sharedInstance].dispatchInterval = [dispatchPeriod doubleValue]; 27 | else 28 | [GAI sharedInstance].dispatchInterval = 30; 29 | 30 | /* 31 | Updated GA tracker interface object as defult tracker, it will support both single and multiple tracker in iOS. 32 | ref: https://developers.google.com/analytics/devguides/collection/ios/v2/advanced#multiple-trackers 33 | Sample Code: 34 | this.ga.startTrackerWithId('YOUR_TRACKER_ID_1') 35 | .then(() => { 36 | // Send a screen view to the first property. 37 | this.ga.trackView('First Tracker'); 38 | }) 39 | .catch(e => console.log('Error starting GoogleAnalytics', e)); 40 | 41 | this.ga2.startTrackerWithId('YOUR_TRACKER_ID_2') 42 | .then(() => { 43 | // Send another screen view to the second property. 44 | this.ga2.trackView('Second Tracker'); 45 | }) 46 | .catch(e => console.log('Error starting GoogleAnalytics', e)); 47 | */ 48 | // [[GAI sharedInstance] trackerWithTrackingId:accountId]; 49 | id tracker = [[GAI sharedInstance] trackerWithTrackingId:accountId]; 50 | [[GAI sharedInstance] setDefaultTracker: tracker]; 51 | 52 | _trackerStarted = true; 53 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 54 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 55 | }]; 56 | /* NSLog(@"successfully started GAI tracker"); */ 57 | } 58 | 59 | - (void) setAllowIDFACollection: (CDVInvokedUrlCommand*) command 60 | { 61 | CDVPluginResult* pluginResult = nil; 62 | if ( ! _trackerStarted) { 63 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Tracker not started"]; 64 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 65 | return; 66 | } 67 | 68 | id tracker = [[GAI sharedInstance] defaultTracker]; 69 | tracker.allowIDFACollection = [[command argumentAtIndex:0 withDefault:@(NO)] boolValue]; 70 | 71 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 72 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 73 | } 74 | 75 | - (void) addCustomDimensionsToTracker: (id)tracker 76 | { 77 | if (_customDimensions) { 78 | for (NSString *key in _customDimensions.allKeys) { 79 | NSString *value = [_customDimensions objectForKey:key]; 80 | 81 | NSNumberFormatter *f = [[NSNumberFormatter alloc] init]; 82 | f.numberStyle = NSNumberFormatterDecimalStyle; 83 | NSNumber *myKey = [f numberFromString:key]; 84 | 85 | /* NSLog(@"Setting tracker dimension slot %@: <%@>", key, value); */ 86 | [tracker set:[GAIFields customDimensionForIndex:myKey.unsignedIntegerValue] 87 | value:value]; 88 | } 89 | } 90 | } 91 | 92 | - (void) getVar: (CDVInvokedUrlCommand*) command 93 | { 94 | [self.commandDelegate runInBackground:^{ 95 | CDVPluginResult* pluginResult = nil; 96 | if ( ! _trackerStarted) { 97 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Tracker not started"]; 98 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 99 | return; 100 | } 101 | 102 | id tracker = [[GAI sharedInstance] defaultTracker]; 103 | NSString* parameter = [command.arguments objectAtIndex:0]; 104 | NSString* result = [tracker get:parameter]; 105 | 106 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:result]; 107 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 108 | }]; 109 | } 110 | 111 | - (void) setVar: (CDVInvokedUrlCommand*) command 112 | { 113 | [self.commandDelegate runInBackground:^{ 114 | CDVPluginResult* pluginResult = nil; 115 | if ( ! _trackerStarted) { 116 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Tracker not started"]; 117 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 118 | return; 119 | } 120 | 121 | id tracker = [[GAI sharedInstance] defaultTracker]; 122 | NSString* parameterName = [command.arguments objectAtIndex:0]; 123 | NSString* parameter = [command.arguments objectAtIndex:1]; 124 | [tracker set:parameterName value:parameter]; 125 | 126 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 127 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 128 | }]; 129 | } 130 | 131 | - (void) dispatch: (CDVInvokedUrlCommand*) command 132 | { 133 | [[GAI sharedInstance] dispatch]; 134 | } 135 | 136 | - (void) debugMode: (CDVInvokedUrlCommand*) command 137 | { 138 | _debugMode = true; 139 | [[GAI sharedInstance].logger setLogLevel:kGAILogLevelVerbose]; 140 | } 141 | 142 | - (void) setUserId: (CDVInvokedUrlCommand*)command 143 | { 144 | CDVPluginResult* pluginResult = nil; 145 | NSString* userId = [command.arguments objectAtIndex:0]; 146 | 147 | if ( ! _trackerStarted) { 148 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Tracker not started"]; 149 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 150 | return; 151 | } 152 | 153 | id tracker = [[GAI sharedInstance] defaultTracker]; 154 | [tracker set:@"&uid" value: userId]; 155 | 156 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 157 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 158 | } 159 | 160 | - (void) setAnonymizeIp: (CDVInvokedUrlCommand*)command 161 | { 162 | CDVPluginResult* pluginResult = nil; 163 | NSString* anonymize = [command.arguments objectAtIndex:0]; 164 | 165 | if ( ! _trackerStarted) { 166 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Tracker not started"]; 167 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 168 | return; 169 | } 170 | 171 | id tracker = [[GAI sharedInstance] defaultTracker]; 172 | [tracker set:kGAIAnonymizeIp value:anonymize]; 173 | 174 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 175 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 176 | } 177 | 178 | - (void) setOptOut: (CDVInvokedUrlCommand*)command 179 | { 180 | CDVPluginResult* pluginResult = nil; 181 | bool optout = [[command.arguments objectAtIndex:0] boolValue]; 182 | 183 | if ( ! _trackerStarted) { 184 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Tracker not started"]; 185 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 186 | return; 187 | } 188 | 189 | [[GAI sharedInstance] setOptOut:optout]; 190 | 191 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 192 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 193 | } 194 | 195 | - (void) setAppVersion: (CDVInvokedUrlCommand*)command 196 | { 197 | CDVPluginResult* pluginResult = nil; 198 | NSString* version = [command.arguments objectAtIndex:0]; 199 | 200 | if ( ! _trackerStarted) { 201 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Tracker not started"]; 202 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 203 | return; 204 | } 205 | 206 | id tracker = [[GAI sharedInstance] defaultTracker]; 207 | [tracker set:@"&av" value: version]; 208 | 209 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 210 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 211 | } 212 | 213 | - (void) enableUncaughtExceptionReporting: (CDVInvokedUrlCommand*)command 214 | { 215 | CDVPluginResult* pluginResult = nil; 216 | 217 | if ( ! _trackerStarted) { 218 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Tracker not started"]; 219 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 220 | return; 221 | } 222 | 223 | bool enabled = [[command.arguments objectAtIndex:0] boolValue]; 224 | [[GAI sharedInstance] setTrackUncaughtExceptions:enabled]; 225 | 226 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 227 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 228 | } 229 | 230 | - (void) addCustomDimension: (CDVInvokedUrlCommand*)command 231 | { 232 | CDVPluginResult* pluginResult = nil; 233 | NSNumber* key = [command.arguments objectAtIndex:0]; 234 | NSString* value = [command.arguments objectAtIndex:1]; 235 | 236 | if ( ! _customDimensions) { 237 | _customDimensions = [[NSMutableDictionary alloc] init]; 238 | } 239 | 240 | _customDimensions[key.stringValue] = value; 241 | 242 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 243 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 244 | } 245 | 246 | - (void) trackMetric: (CDVInvokedUrlCommand*)command 247 | { 248 | CDVPluginResult* pluginResult = nil; 249 | 250 | if ( ! _trackerStarted) { 251 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Tracker not started"]; 252 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 253 | return; 254 | } 255 | 256 | [self.commandDelegate runInBackground:^{ 257 | CDVPluginResult* pluginResult = nil; 258 | NSNumber *key = nil; 259 | NSString *value = nil; 260 | 261 | if ([command.arguments count] > 0) 262 | key = [command.arguments objectAtIndex:0]; 263 | 264 | if ([command.arguments count] > 1) 265 | value = [command.arguments objectAtIndex:1]; 266 | 267 | id tracker = [[GAI sharedInstance] defaultTracker]; 268 | 269 | [tracker set:[GAIFields customMetricForIndex:[key intValue]] value:value]; 270 | 271 | 272 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 273 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 274 | }]; 275 | } 276 | 277 | - (void) trackEvent: (CDVInvokedUrlCommand*)command 278 | { 279 | CDVPluginResult* pluginResult = nil; 280 | 281 | if ( ! _trackerStarted) { 282 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Tracker not started"]; 283 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 284 | return; 285 | } 286 | 287 | [self.commandDelegate runInBackground:^{ 288 | CDVPluginResult* pluginResult = nil; 289 | NSString *category = nil; 290 | NSString *action = nil; 291 | NSString *label = nil; 292 | NSNumber *value = nil; 293 | 294 | if ([command.arguments count] > 0) 295 | category = [command.arguments objectAtIndex:0]; 296 | 297 | if ([command.arguments count] > 1) 298 | action = [command.arguments objectAtIndex:1]; 299 | 300 | if ([command.arguments count] > 2) 301 | label = [command.arguments objectAtIndex:2]; 302 | 303 | if ([command.arguments count] > 3) 304 | value = [command.arguments objectAtIndex:3]; 305 | 306 | bool newSession = [[command argumentAtIndex:4 withDefault:@(NO)] boolValue]; 307 | 308 | id tracker = [[GAI sharedInstance] defaultTracker]; 309 | 310 | [self addCustomDimensionsToTracker:tracker]; 311 | 312 | GAIDictionaryBuilder *builder = [GAIDictionaryBuilder 313 | createEventWithCategory: category //required 314 | action: action //required 315 | label: label 316 | value: value]; 317 | if(newSession){ 318 | [builder set:@"start" forKey:kGAISessionControl]; 319 | } 320 | [tracker send:[builder build]]; 321 | 322 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 323 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 324 | 325 | }]; 326 | 327 | } 328 | 329 | - (void) trackException: (CDVInvokedUrlCommand*)command 330 | { 331 | CDVPluginResult* pluginResult = nil; 332 | 333 | if ( ! _trackerStarted) { 334 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Tracker not started"]; 335 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 336 | return; 337 | } 338 | 339 | [self.commandDelegate runInBackground:^{ 340 | CDVPluginResult* pluginResult = nil; 341 | NSString *description = nil; 342 | NSNumber *fatal = nil; 343 | 344 | if ([command.arguments count] > 0) 345 | description = [command.arguments objectAtIndex:0]; 346 | 347 | if ([command.arguments count] > 1) 348 | fatal = [command.arguments objectAtIndex:1]; 349 | 350 | id tracker = [[GAI sharedInstance] defaultTracker]; 351 | 352 | [self addCustomDimensionsToTracker:tracker]; 353 | 354 | [tracker send:[[GAIDictionaryBuilder createScreenView] build]]; 355 | 356 | [tracker send:[[GAIDictionaryBuilder 357 | createExceptionWithDescription: description 358 | withFatal: fatal] build]]; 359 | 360 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 361 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 362 | }]; 363 | } 364 | 365 | - (void) trackView: (CDVInvokedUrlCommand*)command 366 | { 367 | CDVPluginResult* pluginResult = nil; 368 | 369 | if ( ! _trackerStarted) { 370 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Tracker not started"]; 371 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 372 | return; 373 | } 374 | 375 | [self.commandDelegate runInBackground:^{ 376 | CDVPluginResult* pluginResult = nil; 377 | NSString* screenName = [command.arguments objectAtIndex:0]; 378 | 379 | id tracker = [[GAI sharedInstance] defaultTracker]; 380 | [self addCustomDimensionsToTracker:tracker]; 381 | 382 | NSString* deepLinkUrl = [command.arguments objectAtIndex:1]; 383 | GAIDictionaryBuilder* openParams = [[GAIDictionaryBuilder alloc] init]; 384 | 385 | if (deepLinkUrl && deepLinkUrl != (NSString *)[NSNull null]) { 386 | [[openParams setCampaignParametersFromUrl:deepLinkUrl] build]; 387 | } 388 | 389 | bool newSession = [[command argumentAtIndex:2 withDefault:@(NO)] boolValue]; 390 | if(newSession){ 391 | [openParams set:@"start" forKey:kGAISessionControl]; 392 | } 393 | 394 | NSDictionary *hitParamsDict = [openParams build]; 395 | 396 | [tracker set:kGAIScreenName value:screenName]; 397 | [tracker send:[[[GAIDictionaryBuilder createScreenView] setAll:hitParamsDict] build]]; 398 | 399 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 400 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 401 | }]; 402 | } 403 | 404 | - (void) trackTiming: (CDVInvokedUrlCommand*)command 405 | { 406 | CDVPluginResult* pluginResult = nil; 407 | 408 | if ( ! _trackerStarted) { 409 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Tracker not started"]; 410 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 411 | return; 412 | } 413 | 414 | [self.commandDelegate runInBackground:^{ 415 | CDVPluginResult* pluginResult = nil; 416 | 417 | NSString *category = nil; 418 | NSNumber *intervalInMilliseconds = nil; 419 | NSString *name = nil; 420 | NSString *label = nil; 421 | 422 | if ([command.arguments count] > 0) 423 | category = [command.arguments objectAtIndex:0]; 424 | 425 | if ([command.arguments count] > 1) 426 | intervalInMilliseconds = [command.arguments objectAtIndex:1]; 427 | 428 | if ([command.arguments count] > 2) 429 | name = [command.arguments objectAtIndex:2]; 430 | 431 | if ([command.arguments count] > 3) 432 | label = [command.arguments objectAtIndex:3]; 433 | 434 | id tracker = [[GAI sharedInstance] defaultTracker]; 435 | 436 | [self addCustomDimensionsToTracker:tracker]; 437 | 438 | [tracker send:[[GAIDictionaryBuilder 439 | createTimingWithCategory: category //required 440 | interval: intervalInMilliseconds //required 441 | name: name 442 | label: label] build]]; 443 | 444 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 445 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 446 | }]; 447 | } 448 | 449 | - (void) addTransaction: (CDVInvokedUrlCommand*)command 450 | { 451 | CDVPluginResult* pluginResult = nil; 452 | 453 | if ( ! _trackerStarted) { 454 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Tracker not started"]; 455 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 456 | return; 457 | } 458 | 459 | [self.commandDelegate runInBackground:^{ 460 | CDVPluginResult* pluginResult = nil; 461 | 462 | NSString *transactionId = nil; 463 | NSString *affiliation = nil; 464 | NSNumber *revenue = nil; 465 | NSNumber *tax = nil; 466 | NSNumber *shipping = nil; 467 | NSString *currencyCode = nil; 468 | 469 | 470 | if ([command.arguments count] > 0) 471 | transactionId = [command.arguments objectAtIndex:0]; 472 | 473 | if ([command.arguments count] > 1) 474 | affiliation = [command.arguments objectAtIndex:1]; 475 | 476 | if ([command.arguments count] > 2) 477 | revenue = [command.arguments objectAtIndex:2]; 478 | 479 | if ([command.arguments count] > 3) 480 | tax = [command.arguments objectAtIndex:3]; 481 | 482 | if ([command.arguments count] > 4) 483 | shipping = [command.arguments objectAtIndex:4]; 484 | 485 | if ([command.arguments count] > 5) 486 | currencyCode = [command.arguments objectAtIndex:5]; 487 | 488 | id tracker = [[GAI sharedInstance] defaultTracker]; 489 | 490 | 491 | [tracker send:[[GAIDictionaryBuilder createTransactionWithId:transactionId // (NSString) Transaction ID 492 | affiliation:affiliation // (NSString) Affiliation 493 | revenue:revenue // (NSNumber) Order revenue (including tax and shipping) 494 | tax:tax // (NSNumber) Tax 495 | shipping:shipping // (NSNumber) Shipping 496 | currencyCode:currencyCode] build]]; // (NSString) Currency code 497 | 498 | 499 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 500 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 501 | }]; 502 | } 503 | 504 | 505 | 506 | - (void) addTransactionItem: (CDVInvokedUrlCommand*)command 507 | { 508 | CDVPluginResult* pluginResult = nil; 509 | 510 | if ( ! _trackerStarted) { 511 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Tracker not started"]; 512 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 513 | return; 514 | } 515 | 516 | [self.commandDelegate runInBackground:^{ 517 | 518 | CDVPluginResult* pluginResult = nil; 519 | NSString *transactionId = nil; 520 | NSString *name = nil; 521 | NSString *sku = nil; 522 | NSString *category = nil; 523 | NSNumber *price = nil; 524 | NSNumber *quantity = nil; 525 | NSString *currencyCode = nil; 526 | 527 | 528 | if ([command.arguments count] > 0) 529 | transactionId = [command.arguments objectAtIndex:0]; 530 | 531 | if ([command.arguments count] > 1) 532 | name = [command.arguments objectAtIndex:1]; 533 | 534 | if ([command.arguments count] > 2) 535 | sku = [command.arguments objectAtIndex:2]; 536 | 537 | if ([command.arguments count] > 3) 538 | category = [command.arguments objectAtIndex:3]; 539 | 540 | if ([command.arguments count] > 4) 541 | price = [command.arguments objectAtIndex:4]; 542 | 543 | if ([command.arguments count] > 5) 544 | quantity = [command.arguments objectAtIndex:5]; 545 | 546 | if ([command.arguments count] > 6) 547 | currencyCode = [command.arguments objectAtIndex:6]; 548 | 549 | id tracker = [[GAI sharedInstance] defaultTracker]; 550 | 551 | 552 | [tracker send:[[GAIDictionaryBuilder createItemWithTransactionId:transactionId // (NSString) Transaction ID 553 | name:name // (NSString) Product Name 554 | sku:sku // (NSString) Product SKU 555 | category:category // (NSString) Product category 556 | price:price // (NSNumber) Product price 557 | quantity:quantity // (NSNumber) Product quantity 558 | currencyCode:currencyCode] build]]; // (NSString) Currency code 559 | 560 | 561 | pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 562 | [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; 563 | }]; 564 | } 565 | 566 | @end 567 | -------------------------------------------------------------------------------- /lavaca/AnalyticsService.js: -------------------------------------------------------------------------------- 1 | define(function(require) { 2 | 3 | var Disposable = require('lavaca/util/Disposable'); 4 | var Config = require('lavaca/util/Config'); 5 | var Device = require('lavaca/env/Device'); 6 | var ga = require('google-analytics'); 7 | 8 | var AnalyticsService = Disposable.extend(function AnalyticsService() { 9 | Disposable.call(this); 10 | this.appId = Config.get('google_analytics_id'); 11 | this.webId = Config.get('google_analytics_web_id'); 12 | this.isWeb = this.webId.length; 13 | 14 | if (!Device.isCordova() && this.isWeb) { 15 | ga('create', this.webId); 16 | } 17 | 18 | document.addEventListener('deviceready', this.init.bind(this), false); 19 | }, { 20 | ready: false, 21 | queue: [], 22 | isWeb: false, 23 | //isApp: false, //TODO need to research when Device.isCordova() is valid in Lavaca load flow 24 | init: function() { 25 | if (Device.isCordova() && analytics) { 26 | this.ready = true; 27 | analytics.startTrackerWithId(this.appId); 28 | this.processQueue(); 29 | this.isWeb = false; 30 | } 31 | }, 32 | trackView: function(screen) { 33 | if (Device.isCordova()) { 34 | if (this.ready) { 35 | analytics.trackView(screen); 36 | } else { 37 | this.queue.push({ 38 | action: 'trackView', 39 | params: [screen] 40 | }); 41 | } 42 | } else if (this.isWeb) { 43 | ga('send', 'pageview', { 44 | 'title': screen 45 | }); 46 | } 47 | }, 48 | setUserId: function() { 49 | throw 'setUserId is not implemented for Lavaca'; 50 | }, 51 | setAnonymizeIp: function() { 52 | throw 'setAnonymizeIp is not implemented for Lavaca'; 53 | }, 54 | setOptOut: function() { 55 | throw 'setOptOut is not implemented for Lavaca'; 56 | }, 57 | setAppVersion: function() { 58 | throw 'setAppVersion is not implemented for Lavaca'; 59 | }, 60 | debugMode: function() { 61 | throw 'debugMode is not implemented for Lavaca'; 62 | }, 63 | trackEvent: function(category, action, label, value) { 64 | action = action || ''; 65 | label = label || ''; 66 | value = value || 0; 67 | if (Device.isCordova()) { 68 | if (this.ready) { 69 | analytics.trackEvent(category, action, label, value); 70 | } else { 71 | this.queue.push({ 72 | action: 'trackEvent', 73 | params: [category, action, label, value] 74 | }); 75 | } 76 | } else if (this.isWeb) { 77 | ga('send', { 78 | 'hitType': 'event', 79 | 'eventCategory': category, 80 | 'eventAction': action, 81 | 'eventLabel': label, 82 | 'eventValue': value 83 | }); 84 | } 85 | }, 86 | trackTiming: function(category, intervalInMilliseconds, name, label) { 87 | action = action || ''; 88 | label = label || ''; 89 | value = value || 0; 90 | if (Device.isCordova()) { 91 | if (this.ready) { 92 | analytics.trackTiming(category, intervalInMilliseconds, name, label); 93 | } else { 94 | this.queue.push({ 95 | action: 'trackTiming', 96 | params: [category, intervalInMilliseconds, name, label] 97 | }); 98 | } 99 | } else if (this.isWeb) { 100 | ga('send', { 101 | 'hitType': 'timing', 102 | 'timingCategory': category, 103 | 'timingValue': intervalInMilliseconds, 104 | 'timingVar': name, 105 | 'timingLabel': label 106 | }); 107 | } 108 | }, 109 | processQueue: function() { 110 | if (this.queue) { 111 | var emptyFunction = function() {}; 112 | for (var i = 0; i < this.queue.length; ++i) { 113 | cordova.exec(emptyFunction, emptyFunction, 114 | 'UniversalAnalytics', this.queue[i].action, this.queue[i].params); 115 | } 116 | } 117 | } 118 | }); 119 | 120 | return new AnalyticsService(); 121 | }); 122 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cordova-plugin-google-analytics", 3 | "version": "1.9.3", 4 | "description": "Google Universal Analytics Plugin", 5 | "cordova": { 6 | "id": "cordova-plugin-google-analytics", 7 | "platforms": [ 8 | "android", 9 | "ios", 10 | "windows", 11 | "browser" 12 | ] 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/danwilson/google-analytics-plugin.git" 17 | }, 18 | "keywords": [ 19 | "cordova", 20 | "googleanalytics", 21 | "ecosystem:cordova", 22 | "cordova-android", 23 | "cordova-ios", 24 | "analytics", 25 | "google", 26 | "universal", 27 | "universalanalytics" 28 | ], 29 | "author": "Dan Wilson", 30 | "license": "MIT", 31 | "types": "./typescript/analytics.d.ts" 32 | } 33 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | Google Universal Analytics Plugin 8 | Simple tracking (screens/events) for Google Analytics SDK 3.14 (iOS), SDK 4.0 (Android), Browsers and WP8 9 | Daniel C. Wilson 10 | MIT License 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | You need to install Windows SDK for Google Analytics™ (UWP.SDKforGoogleAnalytics.Native) via nuget package manager 113 | and set target to either x86 or x64! 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /typescript/analytics.d.ts: -------------------------------------------------------------------------------- 1 | declare class UniversalAnalyticsPlugin { 2 | 3 | /** In your 'deviceready' handler, call this to set up your Analytics tracker, 4 | where id is your Google Analytics Mobile App property */ 5 | public startTrackerWithId(id:String, dispatchPeriod?:Number, successCallback?:Function, errorCallback?:Function):void; 6 | 7 | /** Sets a UserId */ 8 | public setUserId(id:String, successCallback?:Function, errorCallback?:Function):void; 9 | 10 | /** Sets a setAnonymizeIp */ 11 | public setAnonymizeIp(anonymize:Boolean, successCallback?:Function, errorCallback?:Function):void; 12 | 13 | /** Sets a setOptOut */ 14 | public setOptOut(optout:Boolean, successCallback?:Function, errorCallback?:Function): void; 15 | 16 | /** Sets a setAllowIDFACollection */ 17 | public setAllowIDFACollection(enable:Boolean, successCallback?:Function, errorCallback?:Function):void; 18 | 19 | /** Sets a AppVersion */ 20 | public setAppVersion(version:string, successCallback?:Function, errorCallback?:Function):void; 21 | 22 | /** Gets a specific variable using this key list https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters */ 23 | public getVar(variable:string, successCallback?:Function, errorCallback?:Function):void; 24 | 25 | /** Sets a specific variable using this key list https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters */ 26 | public setVar(variable:string, value:string, successCallback?:Function, errorCallback?:Function):void; 27 | 28 | /** To manually dispatch any data */ 29 | public dispatch(successCallback?:Function, errorCallback?:Function); 30 | 31 | /** Enables verbose logging */ 32 | public debugMode(successCallback?:Function, errorCallback?:Function):void; 33 | 34 | /** Track a Custom Metric */ 35 | public trackMetric(key:number, value?:number, successCallback?:Function, errorCallback?:Function):void; 36 | 37 | /** Track a Screen (PageView) */ 38 | public trackView(screen:String, campaignUrl?:string, newSession?:boolean, successCallback?:Function, errorCallback?:Function):void; 39 | 40 | /** Add a Custom Dimension */ 41 | public addCustomDimension(key:number, value:String, successCallback?:Function, errorCallback?:Function):void; 42 | 43 | /** Track an Event */ 44 | public trackEvent(category:String, action:String, label?:String, value?:Number, newSession?:boolean, successCallback?:Function, errorCallback?:Function):void; 45 | 46 | /** Track an Exception 47 | https://developers.google.com/analytics/devguides/collection/android/v3/exceptions */ 48 | public trackException(description:String, fatal:Boolean, successCallback?:Function, errorCallback?:Function):void; 49 | 50 | /** Enable/disable automatic reporting of uncaught exceptions */ 51 | public enableUncaughtExceptionReporting(enable:Boolean, successCallback?:Function, errorCallback?:Function):void; 52 | 53 | /** Track User Timing (App Speed) */ 54 | public trackTiming(category:String, intervalInMilliseconds?:Number, name?:String, label?:String, successCallback?:Function, errorCallback?:Function):void; 55 | 56 | // Deprecated on 1.9.0 will be removed on next minor version (1.10.0). 57 | /** Add a Transaction (Google Analytics e-Ccommerce Tracking) 58 | https://developers.google.com/analytics/devguides/collection/analyticsjs/ecommerce */ 59 | public addTransaction(transactionId:String, affiliation:String, revenue:Number, tax:Number, shipping:Number, currencyCode:String, successCallback?:Function, errorCallback?:Function):void; 60 | 61 | // Deprecated on 1.9.0 will be removed on next minor version (1.10.0). 62 | /** Add a Transaction Item (Ecommerce) */ 63 | public addTransactionItem(transactionId:String, name:String, sku:String, category:String, price:Number, quantity:Number, currencyCode:String, successCallback?:Function, errorCallback?:Function):void; 64 | 65 | } 66 | -------------------------------------------------------------------------------- /windows/GoogleAnalyticsProxy.js: -------------------------------------------------------------------------------- 1 | /* global GoogleAnalytics */ 2 | 3 | var _supported = null; // set to null so we can check first time 4 | var _tracker = null; 5 | var _customDimensions = {}; 6 | var _customMetrics = {}; 7 | 8 | function isSupported() { 9 | // if not checked before, run check 10 | if (_supported === null) { 11 | _supported = (GoogleAnalytics && GoogleAnalytics.AnalyticsManager && GoogleAnalytics.AnalyticsManager.current && 12 | GoogleAnalytics.HitBuilder); 13 | } 14 | return _supported; 15 | } 16 | 17 | function isTrackerStarted() { 18 | return (_tracker !== null); 19 | } 20 | 21 | function getAnalyticsManager() { 22 | if (!isSupported()) { 23 | throw new Error("Google Analytics is not supported"); 24 | } 25 | return GoogleAnalytics.AnalyticsManager.current; 26 | } 27 | 28 | function getTracker() { 29 | if (!isSupported()) { 30 | throw new Error("Google Analytics is not supported"); 31 | } else if (!isTrackerStarted()) { 32 | throw new Error("Tracker not started"); 33 | } 34 | return _tracker; 35 | } 36 | 37 | // extended debug support 38 | 39 | function onHitMalformed(args) { 40 | console.warn("**hit malformed** \n" + args.httpStatusCode, parseHit(args.hit)); 41 | } 42 | 43 | function onHitFailed(args) { 44 | console.error("**hit failed**\n" + args.error.message, parseHit(args.hit)); 45 | } 46 | 47 | function onHitSent(args) { 48 | console.log("Analytics result: " + args.response, parseHit(args.hit)); 49 | } 50 | 51 | function parseHit(hit) { 52 | var pair; 53 | var result = ""; 54 | var iter = hit.data.first(); 55 | while (iter.hasCurrent) { 56 | result += iter.current.key; 57 | result += ":"; 58 | result += iter.current.value; 59 | result += "\n"; 60 | iter.moveNext(); 61 | } 62 | return result; 63 | } 64 | 65 | function uncaughtExceptionHandler(err) { 66 | console.error(err); 67 | try { 68 | var hit = GoogleAnalytics.HitBuilder.createException(err.message, true); 69 | 70 | // add previously added custom dimensions and metrics 71 | hit = addCustomDimensionsAndMetrics(hit); 72 | 73 | const data = hit.build(); 74 | getTracker().send(data); 75 | } catch (ex) { 76 | console.error(ex); 77 | } 78 | return true; 79 | } 80 | 81 | function addCustomDimensionsAndMetrics(hitBuilder) { 82 | if (hitBuilder) { 83 | // add previously added custom dimensions 84 | for (var key in _customDimensions) { 85 | if (_customDimensions.hasOwnProperty(key)) { 86 | hitBuilder = hitBuilder.setCustomDimension(key, _customDimensions[key]); 87 | } 88 | } 89 | 90 | // add previously added custom metrics 91 | for (var key in _customMetrics) { 92 | if (_customMetrics.hasOwnProperty(key)) { 93 | hitBuilder = hitBuilder.setCustomMetric(key, _customMetrics[key]); 94 | } 95 | } 96 | } 97 | return hitBuilder; 98 | } 99 | 100 | module.exports = { 101 | 102 | setOptOut: function (win, fail, args) { 103 | if (!args || args.length === 0 || typeof args[0] !== "boolean") { 104 | fail("Expected boolean argument"); 105 | return; 106 | } 107 | 108 | getAnalyticsManager().appOptOut = args[0]; 109 | win(); 110 | }, 111 | 112 | enableUncaughtExceptionReporting: function (win, fail, args) { 113 | if (!args || args.length === 0 || typeof args[0] !== "boolean") { 114 | fail("Expected boolean argument"); 115 | return; 116 | } 117 | 118 | // set analytics property 119 | getAnalyticsManager().reportUncaughtExceptions = args[0]; 120 | 121 | // hook global javascript error handler 122 | if (WinJS && WinJS.Application && typeof WinJS.Application.addEventListener === "function") { 123 | if (args[0]) { 124 | WinJS.Application.addEventListener('error', uncaughtExceptionHandler); 125 | } else { 126 | WinJS.Application.removeEventListener('error', uncaughtExceptionHandler); 127 | } 128 | } 129 | 130 | win(); 131 | }, 132 | 133 | dispatch: function (win, fail, args) { 134 | getAnalyticsManager().dispatchAsync().done(win, fail); 135 | }, 136 | 137 | debugMode: function (win, fail, args) { 138 | const ga = getAnalyticsManager(); 139 | ga.isDebug = true; 140 | 141 | // hook debug events 142 | ga.addEventListener("hitfailed", onHitFailed); 143 | ga.addEventListener("hitsent", onHitSent); 144 | ga.addEventListener("hitmailformed", onHitMalformed); 145 | 146 | win(); 147 | }, 148 | 149 | startTrackerWithId: function (win, fail, args) { 150 | if (!args || args.length === 0 || args[0] === "") { 151 | fail("Expected non empty string argument"); 152 | return; 153 | } 154 | 155 | if (args.length >= 2 && !Number.isInteger(args[1])) { 156 | fail("Expected numeric integer argument"); 157 | return; 158 | } 159 | 160 | if (isTrackerStarted()) { 161 | fail("Tracker already started!"); 162 | return; 163 | } 164 | 165 | const ga = getAnalyticsManager(); 166 | // important! do fire events on ui thread 167 | // (otherwise unhandled exceptions can occur on delayed dispatch..) 168 | ga.fireEventsOnUIThread = true; 169 | 170 | // set dispatch period 171 | if (args.length > 1) { 172 | ga.dispatchPeriod = args[1] * 1000; 173 | } 174 | _tracker = ga.createTracker(args[0]); 175 | win(); 176 | }, 177 | 178 | setUserId: function (win, fail, args) { 179 | if (!args || args.length === 0 || args[0] === "") { 180 | fail("Expected non empty string argument"); 181 | return; 182 | } 183 | 184 | getTracker().clientId = args[0]; 185 | win(); 186 | }, 187 | 188 | setAnonymizeIp: function (win, fail, args) { 189 | if (!args || args.length === 0 || typeof args[0] !== "boolean") { 190 | fail("Expected boolean argument"); 191 | return; 192 | } 193 | 194 | getTracker().anonymizeIP = args[0]; 195 | win(); 196 | }, 197 | 198 | setAllowIDFACollection: function () { 199 | // not supported 200 | fail("not supported on Windows platform"); 201 | }, 202 | 203 | setAppVersion: function (win, fail, args) { 204 | if (!args || args.length === 0 || args[0] === "") { 205 | fail("Expected non empty string argument"); 206 | return; 207 | } 208 | 209 | getTracker().appVersion = args[0]; 210 | win(); 211 | }, 212 | 213 | getVar: function (win, fail, args) { 214 | if (!args || args.length === 0 || args[0] === "") { 215 | fail("Expected non empty string argument"); 216 | return; 217 | } 218 | 219 | const value = getTracker().get(args[0]); 220 | win(value); 221 | }, 222 | 223 | setVar: function (win, fail, args) { 224 | if (!args || args.length === 0 || args[0] === "") { 225 | fail("Expected non empty string argument"); 226 | return; 227 | } 228 | 229 | getTracker().set(args[0], args[1]); 230 | win(); 231 | }, 232 | 233 | trackMetric: function (win, fail, args) { 234 | if (!args || args.length === 0 || !Number.isInteger(args[0]) || args[0] < 0) { 235 | fail("Expected positive numeric integer argument"); 236 | return; 237 | } 238 | 239 | if (args.length < 1 || args[1] === null || typeof args[1] === "undefined" || args[1] === "") { 240 | // unset metric 241 | delete _customMetrics[args[0]]; 242 | win("custom metric stopped"); 243 | } else { 244 | var value = args[1]; 245 | if (typeof args[1] !== "number") { 246 | if (typeof args[1] !== "string") { 247 | fail("Expected either numeric or string formatted number argument"); 248 | return; 249 | } 250 | value = Number.parseFloat(args[1]); 251 | if (isNaN(value)) { 252 | fail("Expected either numeric or string formatted number argument"); 253 | return; 254 | } 255 | } 256 | 257 | _customMetrics[args[0]] = value; 258 | win("custom metric started"); 259 | } 260 | }, 261 | 262 | addCustomDimension: function (win, fail, args) { 263 | if (!args || args.length === 0 || !Number.isInteger(args[0]) || args[0] < 0) { 264 | fail("Expected positive numeric integer argument"); 265 | return; 266 | } 267 | 268 | if (args.length < 1 || args[1] === null || typeof args[1] === "undefined" || args[1] === "") { 269 | // unset dimension 270 | delete _customDimensions[args[0]]; 271 | win("custom dimension stopped"); 272 | } else { 273 | _customDimensions[args[0]] = args[1]; 274 | win("custom dimension started"); 275 | } 276 | }, 277 | 278 | addTransaction: function (win, fail, args) { 279 | // not supported 280 | fail("not supported on Windows platform"); 281 | }, 282 | 283 | addTransactionItem: function (win, fail, args) { 284 | // not supported 285 | fail("not supported on Windows platform"); 286 | }, 287 | 288 | trackView: function (win, fail, args) { 289 | if (!args || args.length === 0 || args[0] === "") { 290 | fail("Expected non empty string argument"); 291 | return; 292 | } 293 | 294 | var hit = GoogleAnalytics.HitBuilder.createScreenView(args[0]); 295 | 296 | // add previously added custom dimensions and metrics 297 | hit = addCustomDimensionsAndMetrics(hit); 298 | 299 | if (args.length >= 2 && args[1] !== "" && getAnalyticsManager().isDebug === true) { 300 | console.warn("Campaign details not supported on Windows platform!"); 301 | } 302 | 303 | if (args.length >= 3 && args[2] === true) { 304 | hit = hit.setNewSession(); 305 | } 306 | 307 | const data = hit.build(); 308 | getTracker().send(data); 309 | win(); 310 | }, 311 | 312 | trackEvent: function (win, fail, args) { 313 | if (!args || args.length < 2 || args[0] === "" || args[1] === "") { 314 | fail("Expected non empty string argument"); 315 | return; 316 | } 317 | 318 | if (args.length >= 4 && !Number.isInteger(args[3])) { 319 | fail("Expected numeric integer argument"); 320 | return; 321 | } 322 | 323 | var hit = GoogleAnalytics.HitBuilder.createCustomEvent(args[0], args[1], args[2] || null, args[3] || 0); 324 | 325 | // add previously added custom dimensions and metrics 326 | hit = addCustomDimensionsAndMetrics(hit); 327 | 328 | const data = hit.build(); 329 | getTracker().send(data); 330 | win(); 331 | }, 332 | 333 | trackException: function (win, fail, args) { 334 | if (!args || args.length === 0 || args[0] === "") { 335 | fail("Expected non empty string argument"); 336 | return; 337 | } 338 | 339 | const fatal = ((args[1] || false) === true); 340 | var hit = GoogleAnalytics.HitBuilder.createException(args[0], fatal); 341 | 342 | // add previously added custom dimensions and metrics 343 | hit = addCustomDimensionsAndMetrics(hit); 344 | 345 | const data = hit.build(); 346 | getTracker().send(data); 347 | win(); 348 | }, 349 | 350 | trackTiming: function (win, fail, args) { 351 | if (!args || args.length === 0 || args[0] === "") { 352 | fail("Expected non empty string argument"); 353 | return; 354 | } 355 | 356 | if (args.length < 2 || !Number.isInteger(args[1])) { 357 | fail("Expected numeric integer argument"); 358 | return; 359 | } 360 | 361 | if (args.length < 3 || args[2] === "") { 362 | fail("Expected non empty string argument"); 363 | return; 364 | } 365 | 366 | var hit = GoogleAnalytics.HitBuilder.createTiming(args[0], args[2] || null, 367 | args[1] || 0, args[3] || null); 368 | 369 | // add previously added custom dimensions and metrics 370 | hit = addCustomDimensionsAndMetrics(hit); 371 | 372 | const data = hit.build(); 373 | getTracker().send(data); 374 | win(); 375 | } 376 | 377 | }; 378 | require("cordova/exec/proxy").add("UniversalAnalytics", module.exports); 379 | -------------------------------------------------------------------------------- /wp8/PhoneHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Xml; 3 | 4 | namespace UniversalAnalyticsPlugin 5 | { 6 | internal static class Helpers 7 | { 8 | public static string GetAppAttribute(string attributeName) 9 | { 10 | try 11 | { 12 | XmlReaderSettings xmlReaderSettings = new XmlReaderSettings(); 13 | xmlReaderSettings.XmlResolver = new XmlXapResolver(); 14 | using (XmlReader xmlReader = XmlReader.Create("WMAppManifest.xml", xmlReaderSettings)) 15 | { 16 | xmlReader.ReadToDescendant("App"); 17 | if (!xmlReader.IsStartElement()) 18 | { 19 | throw new FormatException("WMAppManifest.xml is missing"); 20 | } 21 | return xmlReader.GetAttribute(attributeName); 22 | } 23 | } 24 | catch 25 | { 26 | return null; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /wp8/PhoneNameResolver.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 by Alan Mendelevich 3 | * 4 | * Licensed under MIT license. 5 | * 6 | * See license.txt for details. 7 | */ 8 | 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Linq; 12 | using System.Text; 13 | using System.Text.RegularExpressions; 14 | 15 | namespace UniversalAnalyticsPlugin 16 | { 17 | public static class PhoneNameResolver 18 | { 19 | public static CanonicalPhoneName Resolve(string manufacturer, string model) 20 | { 21 | var manufacturerNormalized = manufacturer.Trim().ToUpper(); 22 | 23 | switch (manufacturerNormalized) 24 | { 25 | case "NOKIA": 26 | case "MICROSOFT": 27 | case "MICROSOFTMDG": 28 | return ResolveNokia(manufacturer, model); 29 | case "HTC": 30 | return ResolveHtc(manufacturer, model); 31 | case "SAMSUNG": 32 | return ResolveSamsung(manufacturer, model); 33 | case "LG": 34 | return ResolveLg(manufacturer, model); 35 | case "HUAWEI": 36 | return ResolveHuawei(manufacturer, model); 37 | default: 38 | return new CanonicalPhoneName() 39 | { 40 | ReportedManufacturer = manufacturer, 41 | ReportedModel = model, 42 | CanonicalManufacturer = manufacturer, 43 | CanonicalModel = model, 44 | IsResolved = false 45 | }; 46 | } 47 | } 48 | 49 | private static CanonicalPhoneName ResolveHuawei(string manufacturer, string model) 50 | { 51 | var modelNormalized = model.Trim().ToUpper(); 52 | 53 | var result = new CanonicalPhoneName() 54 | { 55 | ReportedManufacturer = manufacturer, 56 | ReportedModel = model, 57 | CanonicalManufacturer = "HUAWEI", 58 | CanonicalModel = model, 59 | IsResolved = false 60 | }; 61 | 62 | var lookupValue = modelNormalized; 63 | 64 | if (lookupValue.StartsWith("HUAWEI H883G")) 65 | { 66 | lookupValue = "HUAWEI H883G"; 67 | } 68 | 69 | if (lookupValue.StartsWith("HUAWEI W1")) 70 | { 71 | lookupValue = "HUAWEI W1"; 72 | } 73 | 74 | if (modelNormalized.StartsWith("HUAWEI W2")) 75 | { 76 | lookupValue = "HUAWEI W2"; 77 | } 78 | 79 | if (huaweiLookupTable.ContainsKey(lookupValue)) 80 | { 81 | var modelMetadata = huaweiLookupTable[lookupValue]; 82 | result.CanonicalModel = modelMetadata.CanonicalModel; 83 | result.Comments = modelMetadata.Comments; 84 | result.IsResolved = true; 85 | } 86 | 87 | return result; 88 | } 89 | 90 | private static CanonicalPhoneName ResolveLg(string manufacturer, string model) 91 | { 92 | var modelNormalized = model.Trim().ToUpper(); 93 | 94 | var result = new CanonicalPhoneName() 95 | { 96 | ReportedManufacturer = manufacturer, 97 | ReportedModel = model, 98 | CanonicalManufacturer = "LG", 99 | CanonicalModel = model, 100 | IsResolved = false 101 | }; 102 | 103 | var lookupValue = modelNormalized; 104 | 105 | if (lookupValue.StartsWith("LG-C900")) 106 | { 107 | lookupValue = "LG-C900"; 108 | } 109 | 110 | if (lookupValue.StartsWith("LG-E900")) 111 | { 112 | lookupValue = "LG-E900"; 113 | } 114 | 115 | if (lgLookupTable.ContainsKey(lookupValue)) 116 | { 117 | var modelMetadata = lgLookupTable[lookupValue]; 118 | result.CanonicalModel = modelMetadata.CanonicalModel; 119 | result.Comments = modelMetadata.Comments; 120 | result.IsResolved = true; 121 | } 122 | 123 | return result; 124 | } 125 | 126 | private static CanonicalPhoneName ResolveSamsung(string manufacturer, string model) 127 | { 128 | var modelNormalized = model.Trim().ToUpper(); 129 | 130 | var result = new CanonicalPhoneName() 131 | { 132 | ReportedManufacturer = manufacturer, 133 | ReportedModel = model, 134 | CanonicalManufacturer = "SAMSUNG", 135 | CanonicalModel = model, 136 | IsResolved = false 137 | }; 138 | 139 | var lookupValue = modelNormalized; 140 | 141 | if (lookupValue.StartsWith("GT-S7530")) 142 | { 143 | lookupValue = "GT-S7530"; 144 | } 145 | 146 | if (lookupValue.StartsWith("SGH-I917")) 147 | { 148 | lookupValue = "SGH-I917"; 149 | } 150 | 151 | if (samsungLookupTable.ContainsKey(lookupValue)) 152 | { 153 | var modelMetadata = samsungLookupTable[lookupValue]; 154 | result.CanonicalModel = modelMetadata.CanonicalModel; 155 | result.Comments = modelMetadata.Comments; 156 | result.IsResolved = true; 157 | } 158 | 159 | return result; 160 | } 161 | 162 | private static CanonicalPhoneName ResolveHtc(string manufacturer, string model) 163 | { 164 | var modelNormalized = model.Trim().ToUpper(); 165 | 166 | var result = new CanonicalPhoneName() 167 | { 168 | ReportedManufacturer = manufacturer, 169 | ReportedModel = model, 170 | CanonicalManufacturer = "HTC", 171 | CanonicalModel = model, 172 | IsResolved = false 173 | }; 174 | 175 | var lookupValue = modelNormalized; 176 | 177 | if (lookupValue.StartsWith("A620")) 178 | { 179 | lookupValue = "A620"; 180 | } 181 | 182 | if (lookupValue.StartsWith("C625")) 183 | { 184 | lookupValue = "C625"; 185 | } 186 | 187 | if (lookupValue.StartsWith("C620")) 188 | { 189 | lookupValue = "C620"; 190 | } 191 | 192 | if (htcLookupTable.ContainsKey(lookupValue)) 193 | { 194 | var modelMetadata = htcLookupTable[lookupValue]; 195 | result.CanonicalModel = modelMetadata.CanonicalModel; 196 | result.Comments = modelMetadata.Comments; 197 | result.IsResolved = true; 198 | } 199 | 200 | return result; 201 | } 202 | 203 | private static CanonicalPhoneName ResolveNokia(string manufacturer, string model) 204 | { 205 | var modelNormalized = model.Trim().ToUpper(); 206 | 207 | var result = new CanonicalPhoneName() 208 | { 209 | ReportedManufacturer = manufacturer, 210 | ReportedModel = model, 211 | CanonicalManufacturer = "NOKIA", 212 | CanonicalModel = model, 213 | IsResolved = false 214 | }; 215 | 216 | var lookupValue = modelNormalized; 217 | if (modelNormalized.StartsWith("RM-")) 218 | { 219 | var rms = Regex.Match(modelNormalized, "(RM-)([0-9]+)"); 220 | lookupValue = rms.Value; 221 | } 222 | 223 | if (nokiaLookupTable.ContainsKey(lookupValue)) 224 | { 225 | var modelMetadata = nokiaLookupTable[lookupValue]; 226 | 227 | if (!string.IsNullOrEmpty(modelMetadata.CanonicalManufacturer)) 228 | { 229 | result.CanonicalManufacturer = modelMetadata.CanonicalManufacturer; 230 | } 231 | result.CanonicalModel = modelMetadata.CanonicalModel; 232 | result.Comments = modelMetadata.Comments; 233 | result.IsResolved = true; 234 | } 235 | 236 | return result; 237 | } 238 | 239 | 240 | private static Dictionary huaweiLookupTable = new Dictionary() 241 | { 242 | // Huawei W1 243 | { "HUAWEI H883G", new CanonicalPhoneName() { CanonicalModel = "Ascend W1" } }, 244 | { "HUAWEI W1", new CanonicalPhoneName() { CanonicalModel = "Ascend W1" } }, 245 | 246 | // Huawei Ascend W2 247 | { "HUAWEI W2", new CanonicalPhoneName() { CanonicalModel = "Ascend W2" } }, 248 | }; 249 | 250 | private static Dictionary lgLookupTable = new Dictionary() 251 | { 252 | // Optimus 7Q/Quantum 253 | { "LG-C900", new CanonicalPhoneName() { CanonicalModel = "Optimus 7Q/Quantum" } }, 254 | 255 | // Optimus 7 256 | { "LG-E900", new CanonicalPhoneName() { CanonicalModel = "Optimus 7" } }, 257 | 258 | // Jil Sander 259 | { "LG-E906", new CanonicalPhoneName() { CanonicalModel = "Jil Sander" } }, 260 | 261 | // Lancet 262 | { "LGVW820", new CanonicalPhoneName() { CanonicalModel = "Lancet" } }, 263 | }; 264 | 265 | private static Dictionary samsungLookupTable = new Dictionary() 266 | { 267 | // OMNIA W 268 | { "GT-I8350", new CanonicalPhoneName() { CanonicalModel = "Omnia W" } }, 269 | { "GT-I8350T", new CanonicalPhoneName() { CanonicalModel = "Omnia W" } }, 270 | { "OMNIA W", new CanonicalPhoneName() { CanonicalModel = "Omnia W" } }, 271 | 272 | // OMNIA 7 273 | { "GT-I8700", new CanonicalPhoneName() { CanonicalModel = "Omnia 7" } }, 274 | { "OMNIA7", new CanonicalPhoneName() { CanonicalModel = "Omnia 7" } }, 275 | 276 | // OMNIA M 277 | { "GT-S7530", new CanonicalPhoneName() { CanonicalModel = "Omnia 7" } }, 278 | 279 | // Focus 280 | { "I917", new CanonicalPhoneName() { CanonicalModel = "Focus" } }, 281 | { "SGH-I917", new CanonicalPhoneName() { CanonicalModel = "Focus" } }, 282 | 283 | // Focus 2 284 | { "SGH-I667", new CanonicalPhoneName() { CanonicalModel = "Focus 2" } }, 285 | 286 | // Focus Flash 287 | { "SGH-I677", new CanonicalPhoneName() { CanonicalModel = "Focus Flash" } }, 288 | 289 | // Focus S 290 | { "HADEN", new CanonicalPhoneName() { CanonicalModel = "Focus S" } }, 291 | { "SGH-I937", new CanonicalPhoneName() { CanonicalModel = "Focus S" } }, 292 | 293 | // ATIV S 294 | { "GT-I8750", new CanonicalPhoneName() { CanonicalModel = "ATIV S" } }, 295 | { "SGH-T899M", new CanonicalPhoneName() { CanonicalModel = "ATIV S" } }, 296 | 297 | // ATIV Odyssey 298 | { "SCH-I930", new CanonicalPhoneName() { CanonicalModel = "ATIV Odyssey" } }, 299 | { "SCH-R860U", new CanonicalPhoneName() { CanonicalModel = "ATIV Odyssey", Comments="US Cellular" } }, 300 | 301 | // ATIV S Neo 302 | { "SPH-I800", new CanonicalPhoneName() { CanonicalModel = "ATIV S Neo", Comments="Sprint" } }, 303 | { "SGH-I187", new CanonicalPhoneName() { CanonicalModel = "ATIV S Neo", Comments="AT&T" } }, 304 | { "GT-I8675", new CanonicalPhoneName() { CanonicalModel = "ATIV S Neo" } }, 305 | 306 | // ATIV SE 307 | { "SM-W750V", new CanonicalPhoneName() { CanonicalModel = "ATIV SE", Comments="Verizon" } }, 308 | }; 309 | 310 | private static Dictionary htcLookupTable = new Dictionary() 311 | { 312 | // Surround 313 | { "7 MONDRIAN T8788", new CanonicalPhoneName() { CanonicalModel = "Surround" } }, 314 | { "T8788", new CanonicalPhoneName() { CanonicalModel = "Surround" } }, 315 | { "SURROUND", new CanonicalPhoneName() { CanonicalModel = "Surround" } }, 316 | { "SURROUND T8788", new CanonicalPhoneName() { CanonicalModel = "Surround" } }, 317 | 318 | // Mozart 319 | { "7 MOZART", new CanonicalPhoneName() { CanonicalModel = "Mozart" } }, 320 | { "7 MOZART T8698", new CanonicalPhoneName() { CanonicalModel = "Mozart" } }, 321 | { "HTC MOZART", new CanonicalPhoneName() { CanonicalModel = "Mozart" } }, 322 | { "MERSAD 7 MOZART T8698", new CanonicalPhoneName() { CanonicalModel = "Mozart" } }, 323 | { "MOZART", new CanonicalPhoneName() { CanonicalModel = "Mozart" } }, 324 | { "MOZART T8698", new CanonicalPhoneName() { CanonicalModel = "Mozart" } }, 325 | { "PD67100", new CanonicalPhoneName() { CanonicalModel = "Mozart" } }, 326 | { "T8697", new CanonicalPhoneName() { CanonicalModel = "Mozart" } }, 327 | 328 | // Pro 329 | { "7 PRO T7576", new CanonicalPhoneName() { CanonicalModel = "7 Pro" } }, 330 | { "MWP6885", new CanonicalPhoneName() { CanonicalModel = "7 Pro" } }, 331 | { "USCCHTC-PC93100", new CanonicalPhoneName() { CanonicalModel = "7 Pro" } }, 332 | 333 | // Arrive 334 | { "PC93100", new CanonicalPhoneName() { CanonicalModel = "Arrive", Comments = "Sprint" } }, 335 | { "T7575", new CanonicalPhoneName() { CanonicalModel = "Arrive", Comments = "Sprint" } }, 336 | 337 | // HD2 338 | { "HD2", new CanonicalPhoneName() { CanonicalModel = "HD2" } }, 339 | { "HD2 LEO", new CanonicalPhoneName() { CanonicalModel = "HD2" } }, 340 | { "LEO", new CanonicalPhoneName() { CanonicalModel = "HD2" } }, 341 | 342 | // HD7 343 | { "7 SCHUBERT T9292", new CanonicalPhoneName() { CanonicalModel = "HD7" } }, 344 | { "GOLD", new CanonicalPhoneName() { CanonicalModel = "HD7" } }, 345 | { "HD7", new CanonicalPhoneName() { CanonicalModel = "HD7" } }, 346 | { "HD7 T9292", new CanonicalPhoneName() { CanonicalModel = "HD7" } }, 347 | { "MONDRIAN", new CanonicalPhoneName() { CanonicalModel = "HD7" } }, 348 | { "SCHUBERT", new CanonicalPhoneName() { CanonicalModel = "HD7" } }, 349 | { "Schubert T9292", new CanonicalPhoneName() { CanonicalModel = "HD7" } }, 350 | { "T9296", new CanonicalPhoneName() { CanonicalModel = "HD7", Comments = "Telstra, AU" } }, 351 | { "TOUCH-IT HD7", new CanonicalPhoneName() { CanonicalModel = "HD7" } }, 352 | 353 | // HD7S 354 | { "T9295", new CanonicalPhoneName() { CanonicalModel = "HD7S" } }, 355 | 356 | // Trophy 357 | { "7 TROPHY", new CanonicalPhoneName() { CanonicalModel = "Trophy" } }, 358 | { "7 TROPHY T8686", new CanonicalPhoneName() { CanonicalModel = "Trophy" } }, 359 | { "PC40100", new CanonicalPhoneName() { CanonicalModel = "Trophy", Comments = "Verizon" } }, 360 | { "SPARK", new CanonicalPhoneName() { CanonicalModel = "Trophy" } }, 361 | { "TOUCH-IT TROPHY", new CanonicalPhoneName() { CanonicalModel = "Trophy" } }, 362 | { "MWP6985", new CanonicalPhoneName() { CanonicalModel = "Trophy" } }, 363 | 364 | // 8S 365 | { "A620", new CanonicalPhoneName() { CanonicalModel = "8S" } }, 366 | { "WINDOWS PHONE 8S BY HTC", new CanonicalPhoneName() { CanonicalModel = "8S" } }, 367 | 368 | // 8X 369 | { "C620", new CanonicalPhoneName() { CanonicalModel = "8X" } }, 370 | { "C625", new CanonicalPhoneName() { CanonicalModel = "8X" } }, 371 | { "HTC6990LVW", new CanonicalPhoneName() { CanonicalModel = "8X", Comments="Verizon" } }, 372 | { "PM23300", new CanonicalPhoneName() { CanonicalModel = "8X", Comments="AT&T" } }, 373 | { "WINDOWS PHONE 8X BY HTC", new CanonicalPhoneName() { CanonicalModel = "8X" } }, 374 | 375 | // 8XT 376 | { "HTCPO881", new CanonicalPhoneName() { CanonicalModel = "8XT", Comments="Sprint" } }, 377 | { "HTCPO881 SPRINT", new CanonicalPhoneName() { CanonicalModel = "8XT", Comments="Sprint" } }, 378 | { "HTCPO881 HTC", new CanonicalPhoneName() { CanonicalModel = "8XT", Comments="Sprint" } }, 379 | 380 | // Titan 381 | { "ETERNITY", new CanonicalPhoneName() { CanonicalModel = "Titan", Comments = "China" } }, 382 | { "PI39100", new CanonicalPhoneName() { CanonicalModel = "Titan", Comments = "AT&T" } }, 383 | { "TITAN X310E", new CanonicalPhoneName() { CanonicalModel = "Titan" } }, 384 | { "ULTIMATE", new CanonicalPhoneName() { CanonicalModel = "Titan" } }, 385 | { "X310E", new CanonicalPhoneName() { CanonicalModel = "Titan" } }, 386 | { "X310E TITAN", new CanonicalPhoneName() { CanonicalModel = "Titan" } }, 387 | 388 | // Titan II 389 | { "PI86100", new CanonicalPhoneName() { CanonicalModel = "Titan II", Comments = "AT&T" } }, 390 | { "RADIANT", new CanonicalPhoneName() { CanonicalModel = "Titan II" } }, 391 | 392 | // Radar 393 | { "RADAR", new CanonicalPhoneName() { CanonicalModel = "Radar" } }, 394 | { "RADAR 4G", new CanonicalPhoneName() { CanonicalModel = "Radar", Comments = "T-Mobile USA" } }, 395 | { "RADAR C110E", new CanonicalPhoneName() { CanonicalModel = "Radar" } }, 396 | 397 | // One M8 398 | { "HTC6995LVW", new CanonicalPhoneName() { CanonicalModel = "One (M8)", Comments="Verizon" } }, 399 | { "0P6B180", new CanonicalPhoneName() { CanonicalModel = "One (M8)", Comments="AT&T" } }, 400 | { "0P6B140", new CanonicalPhoneName() { CanonicalModel = "One (M8)", Comments="Dual SIM?" } }, 401 | }; 402 | 403 | private static Dictionary nokiaLookupTable = new Dictionary() 404 | { 405 | // Lumia 505 406 | { "LUMIA 505", new CanonicalPhoneName() { CanonicalModel = "Lumia 505" } }, 407 | // Lumia 510 408 | { "LUMIA 510", new CanonicalPhoneName() { CanonicalModel = "Lumia 510" } }, 409 | { "NOKIA 510", new CanonicalPhoneName() { CanonicalModel = "Lumia 510" } }, 410 | // Lumia 610 411 | { "LUMIA 610", new CanonicalPhoneName() { CanonicalModel = "Lumia 610" } }, 412 | { "LUMIA 610 NFC", new CanonicalPhoneName() { CanonicalModel = "Lumia 610", Comments = "NFC" } }, 413 | { "NOKIA 610", new CanonicalPhoneName() { CanonicalModel = "Lumia 610" } }, 414 | { "NOKIA 610C", new CanonicalPhoneName() { CanonicalModel = "Lumia 610" } }, 415 | // Lumia 620 416 | { "LUMIA 620", new CanonicalPhoneName() { CanonicalModel = "Lumia 620" } }, 417 | { "RM-846", new CanonicalPhoneName() { CanonicalModel = "Lumia 620" } }, 418 | // Lumia 710 419 | { "LUMIA 710", new CanonicalPhoneName() { CanonicalModel = "Lumia 710" } }, 420 | { "NOKIA 710", new CanonicalPhoneName() { CanonicalModel = "Lumia 710" } }, 421 | // Lumia 800 422 | { "LUMIA 800", new CanonicalPhoneName() { CanonicalModel = "Lumia 800" } }, 423 | { "LUMIA 800C", new CanonicalPhoneName() { CanonicalModel = "Lumia 800" } }, 424 | { "NOKIA 800", new CanonicalPhoneName() { CanonicalModel = "Lumia 800" } }, 425 | { "NOKIA 800C", new CanonicalPhoneName() { CanonicalModel = "Lumia 800", Comments = "China" } }, 426 | // Lumia 810 427 | { "RM-878", new CanonicalPhoneName() { CanonicalModel = "Lumia 810" } }, 428 | // Lumia 820 429 | { "RM-824", new CanonicalPhoneName() { CanonicalModel = "Lumia 820" } }, 430 | { "RM-825", new CanonicalPhoneName() { CanonicalModel = "Lumia 820" } }, 431 | { "RM-826", new CanonicalPhoneName() { CanonicalModel = "Lumia 820" } }, 432 | // Lumia 822 433 | { "RM-845", new CanonicalPhoneName() { CanonicalModel = "Lumia 822", Comments = "Verizon" } }, 434 | // Lumia 900 435 | { "LUMIA 900", new CanonicalPhoneName() { CanonicalModel = "Lumia 900" } }, 436 | { "NOKIA 900", new CanonicalPhoneName() { CanonicalModel = "Lumia 900" } }, 437 | // Lumia 920 438 | { "RM-820", new CanonicalPhoneName() { CanonicalModel = "Lumia 920" } }, 439 | { "RM-821", new CanonicalPhoneName() { CanonicalModel = "Lumia 920" } }, 440 | { "RM-822", new CanonicalPhoneName() { CanonicalModel = "Lumia 920" } }, 441 | { "RM-867", new CanonicalPhoneName() { CanonicalModel = "Lumia 920", Comments = "920T" } }, 442 | { "NOKIA 920", new CanonicalPhoneName() { CanonicalModel = "Lumia 920" } }, 443 | { "LUMIA 920", new CanonicalPhoneName() { CanonicalModel = "Lumia 920" } }, 444 | // Lumia 520 445 | { "RM-914", new CanonicalPhoneName() { CanonicalModel = "Lumia 520" } }, 446 | { "RM-915", new CanonicalPhoneName() { CanonicalModel = "Lumia 520" } }, 447 | { "RM-913", new CanonicalPhoneName() { CanonicalModel = "Lumia 520", Comments="520T" } }, 448 | // Lumia 521? 449 | { "RM-917", new CanonicalPhoneName() { CanonicalModel = "Lumia 521", Comments="T-Mobile 520" } }, 450 | // Lumia 720 451 | { "RM-885", new CanonicalPhoneName() { CanonicalModel = "Lumia 720" } }, 452 | { "RM-887", new CanonicalPhoneName() { CanonicalModel = "Lumia 720", Comments="China 720T" } }, 453 | // Lumia 928 454 | { "RM-860", new CanonicalPhoneName() { CanonicalModel = "Lumia 928" } }, 455 | // Lumia 925 456 | { "RM-892", new CanonicalPhoneName() { CanonicalModel = "Lumia 925" } }, 457 | { "RM-893", new CanonicalPhoneName() { CanonicalModel = "Lumia 925" } }, 458 | { "RM-910", new CanonicalPhoneName() { CanonicalModel = "Lumia 925" } }, 459 | { "RM-955", new CanonicalPhoneName() { CanonicalModel = "Lumia 925", Comments="China 925T" } }, 460 | // Lumia 1020 461 | { "RM-875", new CanonicalPhoneName() { CanonicalModel = "Lumia 1020" } }, 462 | { "RM-876", new CanonicalPhoneName() { CanonicalModel = "Lumia 1020" } }, 463 | { "RM-877", new CanonicalPhoneName() { CanonicalModel = "Lumia 1020" } }, 464 | // Lumia 625 465 | { "RM-941", new CanonicalPhoneName() { CanonicalModel = "Lumia 625" } }, 466 | { "RM-942", new CanonicalPhoneName() { CanonicalModel = "Lumia 625" } }, 467 | { "RM-943", new CanonicalPhoneName() { CanonicalModel = "Lumia 625" } }, 468 | // Lumia 1520 469 | { "RM-937", new CanonicalPhoneName() { CanonicalModel = "Lumia 1520" } }, 470 | { "RM-938", new CanonicalPhoneName() { CanonicalModel = "Lumia 1520", Comments="AT&T" } }, 471 | { "RM-939", new CanonicalPhoneName() { CanonicalModel = "Lumia 1520" } }, 472 | { "RM-940", new CanonicalPhoneName() { CanonicalModel = "Lumia 1520", Comments="AT&T" } }, 473 | // Lumia 525 474 | { "RM-998", new CanonicalPhoneName() { CanonicalModel = "Lumia 525" } }, 475 | // Lumia 1320 476 | { "RM-994", new CanonicalPhoneName() { CanonicalModel = "Lumia 1320" } }, 477 | { "RM-995", new CanonicalPhoneName() { CanonicalModel = "Lumia 1320" } }, 478 | { "RM-996", new CanonicalPhoneName() { CanonicalModel = "Lumia 1320" } }, 479 | // Lumia Icon 480 | { "RM-927", new CanonicalPhoneName() { CanonicalModel = "Lumia Icon", Comments="Verizon" } }, 481 | // Lumia 630 482 | { "RM-976", new CanonicalPhoneName() { CanonicalModel = "Lumia 630" } }, 483 | { "RM-977", new CanonicalPhoneName() { CanonicalModel = "Lumia 630" } }, 484 | { "RM-978", new CanonicalPhoneName() { CanonicalModel = "Lumia 630" } }, 485 | { "RM-979", new CanonicalPhoneName() { CanonicalModel = "Lumia 630" } }, 486 | // Lumia 635 487 | { "RM-974", new CanonicalPhoneName() { CanonicalModel = "Lumia 635" } }, 488 | { "RM-975", new CanonicalPhoneName() { CanonicalModel = "Lumia 635" } }, 489 | { "RM-1078", new CanonicalPhoneName() { CanonicalModel = "Lumia 635", Comments="Sprint" } }, 490 | // Lumia 526 491 | { "RM-997", new CanonicalPhoneName() { CanonicalModel = "Lumia 526", Comments="China Mobile" } }, 492 | // Lumia 930 493 | { "RM-1045", new CanonicalPhoneName() { CanonicalModel = "Lumia 930" } }, 494 | { "RM-1087", new CanonicalPhoneName() { CanonicalModel = "Lumia 930" } }, 495 | // Lumia 636 496 | { "RM-1027", new CanonicalPhoneName() { CanonicalModel = "Lumia 636", Comments="China" } }, 497 | // Lumia 638 498 | { "RM-1010", new CanonicalPhoneName() { CanonicalModel = "Lumia 638", Comments="China" } }, 499 | // Lumia 530 500 | { "RM-1017", new CanonicalPhoneName() { CanonicalModel = "Lumia 530", Comments="Single SIM" } }, 501 | { "RM-1018", new CanonicalPhoneName() { CanonicalModel = "Lumia 530", Comments="Single SIM" } }, 502 | { "RM-1019", new CanonicalPhoneName() { CanonicalModel = "Lumia 530", Comments="Dual SIM" } }, 503 | { "RM-1020", new CanonicalPhoneName() { CanonicalModel = "Lumia 530", Comments="Dual SIM" } }, 504 | // Lumia 730 505 | { "RM-1040", new CanonicalPhoneName() { CanonicalModel = "Lumia 730", Comments="Dual SIM" } }, 506 | // Lumia 735 507 | { "RM-1038", new CanonicalPhoneName() { CanonicalModel = "Lumia 735" } }, 508 | { "RM-1039", new CanonicalPhoneName() { CanonicalModel = "Lumia 735" } }, 509 | { "RM-1041", new CanonicalPhoneName() { CanonicalModel = "Lumia 735", Comments="Verizon" } }, 510 | // Lumia 830 511 | { "RM-983", new CanonicalPhoneName() { CanonicalModel = "Lumia 830" } }, 512 | { "RM-984", new CanonicalPhoneName() { CanonicalModel = "Lumia 830" } }, 513 | { "RM-985", new CanonicalPhoneName() { CanonicalModel = "Lumia 830" } }, 514 | { "RM-1049", new CanonicalPhoneName() { CanonicalModel = "Lumia 830" } }, 515 | // Lumia 535 516 | { "RM-1089", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 535" } }, 517 | { "RM-1090", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 535" } }, 518 | { "RM-1091", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 535" } }, 519 | { "RM-1092", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 535" } }, 520 | // Lumia 435 521 | { "RM-1068", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 435", Comments="DS" } }, 522 | { "RM-1069", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 435", Comments="DS" } }, 523 | { "RM-1070", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 435", Comments="DS" } }, 524 | { "RM-1071", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 435", Comments="DS" } }, 525 | { "RM-1114", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 435", Comments="DS" } }, 526 | // Lumia 532 527 | { "RM-1031", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 532", Comments="DS" } }, 528 | { "RM-1032", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 532", Comments="DS" } }, 529 | { "RM-1034", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 532", Comments="DS" } }, 530 | { "RM-1115", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 532", Comments="DS" } }, 531 | // Lumia 640 532 | { "RM-1072", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 640" } }, 533 | { "RM-1073", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 640" } }, 534 | { "RM-1074", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 640" } }, 535 | { "RM-1075", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 640" } }, 536 | { "RM-1077", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 640" } }, 537 | { "RM-1109", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 640" } }, 538 | { "RM-1113", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 640" } }, 539 | // Lumia 640XL 540 | { "RM-1062", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 640 XL" } }, 541 | { "RM-1063", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 640 XL" } }, 542 | { "RM-1064", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 640 XL" } }, 543 | { "RM-1065", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 640 XL" } }, 544 | { "RM-1066", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 640 XL" } }, 545 | { "RM-1067", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 640 XL" } }, 546 | { "RM-1096", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 640 XL" } }, 547 | // Lumia 540 548 | { "RM-1140", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 540" } }, 549 | { "RM-1141", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 540" } }, 550 | // Lumia 430 551 | { "RM-1099", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 430", Comments="DS" } }, 552 | // Lumia 950 553 | { "RM-1104", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 950", Comments="DS" } }, 554 | { "RM-1105", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 950", Comments="DS" } }, 555 | { "RM-1118", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 950", Comments="DS" } }, 556 | // Lumia 950 XL 557 | { "RM-1085", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 950 XL" } }, 558 | { "RM-1116", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 950 XL", Comments="DS" } }, 559 | // Lumia 550 560 | { "RM-1127", new CanonicalPhoneName() { CanonicalManufacturer="MICROSOFT", CanonicalModel = "Lumia 550" } }, 561 | }; 562 | } 563 | 564 | public class CanonicalPhoneName 565 | { 566 | public string ReportedManufacturer { get; set; } 567 | public string ReportedModel { get; set; } 568 | public string CanonicalManufacturer { get; set; } 569 | public string CanonicalModel { get; set; } 570 | public string Comments { get; set; } 571 | public bool IsResolved { get; set; } 572 | 573 | public string FullCanonicalName 574 | { 575 | get { return CanonicalManufacturer + " " + CanonicalModel; } 576 | } 577 | } 578 | } -------------------------------------------------------------------------------- /wp8/PlatformInfoProvider.WP.cs: -------------------------------------------------------------------------------- 1 | // PlatformInfoProvider.WP.cs 2 | 3 | using GoogleAnalytics.Core; 4 | using System; 5 | using System.IO.IsolatedStorage; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | 9 | namespace UniversalAnalyticsPlugin 10 | { 11 | public sealed class PlatformInfoProvider : IPlatformInfoProvider 12 | { 13 | const string Key_AnonymousClientId = "GoogleAnaltyics.AnonymousClientId"; 14 | string anonymousClientId; 15 | Dimensions _screenResolution = new Dimensions(0,0); 16 | 17 | #pragma warning disable 0067 18 | public event EventHandler ViewPortResolutionChanged; 19 | 20 | public event EventHandler ScreenResolutionChanged; 21 | #pragma warning restore 0067 22 | 23 | public PlatformInfoProvider() 24 | { 25 | Deployment.Current.Dispatcher.BeginInvoke(() => { 26 | double scale = (double)Application.Current.Host.Content.ScaleFactor / 100; 27 | int h = (int)Math.Ceiling(Application.Current.Host.Content.ActualHeight * scale); 28 | int w = (int)Math.Ceiling(Application.Current.Host.Content.ActualWidth * scale); 29 | _screenResolution = new Dimensions(h, w); 30 | }); 31 | } 32 | 33 | public string AnonymousClientId 34 | { 35 | get 36 | { 37 | if (anonymousClientId == null) 38 | { 39 | var appSettings = IsolatedStorageSettings.ApplicationSettings; 40 | if (!appSettings.Contains(Key_AnonymousClientId)) 41 | { 42 | anonymousClientId = Guid.NewGuid().ToString(); 43 | appSettings.Add(Key_AnonymousClientId, anonymousClientId); 44 | appSettings.Save(); 45 | } 46 | else 47 | { 48 | anonymousClientId = (string)appSettings[Key_AnonymousClientId]; 49 | } 50 | } 51 | return anonymousClientId; 52 | } 53 | set { anonymousClientId = value; } 54 | } 55 | 56 | public Dimensions ScreenResolution 57 | { 58 | get 59 | { 60 | return _screenResolution; 61 | } 62 | } 63 | 64 | public Dimensions ViewPortResolution 65 | { 66 | get { return ScreenResolution; } 67 | } 68 | 69 | public string UserLanguage 70 | { 71 | get { return System.Globalization.CultureInfo.CurrentUICulture.Name; } 72 | } 73 | 74 | public int? ScreenColorDepthBits 75 | { 76 | get { return null; } 77 | } 78 | 79 | public void OnTracking() 80 | { } 81 | 82 | public string GetUserAgent() 83 | { 84 | var sysInfo = UniversalAnalyticsPlugin.PhoneNameResolver.Resolve(Microsoft.Phone.Info.DeviceStatus.DeviceManufacturer, Microsoft.Phone.Info.DeviceStatus.DeviceName); 85 | Version osVer = Environment.OSVersion.Version; 86 | string uaMask; 87 | 88 | if (osVer.Minor == 10) 89 | { 90 | // Windows Phone 8.1 91 | uaMask = "Mozilla/5.0 (Windows Phone 8.1; ARM; Trident/7.0; Touch; rv11.0; IEMobile/11.0; {1}; {2}) like Gecko"; 92 | } 93 | else 94 | { 95 | // Windows Phone 8.0 96 | uaMask = "Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone OS {0}; Trident/6.0; IEMobile/10.0; ARM; Touch; {1}; {2})"; 97 | } 98 | 99 | //var userAgentMask = "Mozilla/[version] ([system and browser information]) [platform] ([platform details]) [extensions]"; 100 | return string.Format(uaMask, Environment.OSVersion.Version, sysInfo.CanonicalManufacturer, sysInfo.CanonicalModel); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /wp8/UniversalAnalytics.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Dan Polivy 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | * this software and associated documentation files (the "Software"), to deal in 6 | * the Software without restriction, including without limitation the rights to 7 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | * the Software, and to permit persons to whom the Software is furnished to do so, 9 | * subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | using System; 23 | using GoogleAnalytics.Core; 24 | using WPCordovaClassLib.Cordova; 25 | using WPCordovaClassLib.Cordova.Commands; 26 | using WPCordovaClassLib.Cordova.JSON; 27 | using System.Collections.Generic; 28 | using System.Windows; 29 | using System.Diagnostics; 30 | using System.Threading.Tasks; 31 | using Microsoft.Phone.Shell; 32 | 33 | namespace Cordova.Extension.Commands 34 | { 35 | /// 36 | /// UniversalAnalytics plugin class containing methods called from JavaScript 37 | /// 38 | public class UniversalAnalytics : BaseCommand 39 | { 40 | private TrackerManager _trackerManager = new TrackerManager(new UniversalAnalyticsPlugin.PlatformInfoProvider()); 41 | private Tracker _tracker; 42 | private bool _trackerStarted = false; 43 | DateTime? _suspended; 44 | private IDictionary _customDimensions = new Dictionary(); 45 | 46 | public UniversalAnalytics() 47 | { 48 | this.AutoAppLifetimeTracking = false; 49 | this.SessionTimeout = 30; 50 | } 51 | 52 | /// 53 | /// Session timeout length, in seconds. 54 | /// 55 | public int? SessionTimeout { 56 | get; 57 | set; 58 | } 59 | 60 | /// 61 | /// Determines whether events are automatically sent for app lifetime tracking. 62 | /// 63 | public bool AutoAppLifetimeTracking { get; set; } 64 | 65 | public void startTrackerWithId(string options) 66 | { 67 | // If the tracker is already started, don't start it again 68 | if (_trackerStarted) 69 | { 70 | DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Tracker is already started")); 71 | return; 72 | } 73 | 74 | string[] args = JsonHelper.Deserialize(options); 75 | 76 | if (!_trackerStarted && args.Length > 0 && args[0].Length > 0) 77 | { 78 | _tracker = _trackerManager.GetTracker(args[0]); 79 | 80 | // Set additional Tracker parameters here 81 | _tracker.SetStartSession(true); 82 | _tracker.IsUseSecure = true; 83 | _tracker.AppName = UniversalAnalyticsPlugin.Helpers.GetAppAttribute("Title"); 84 | _tracker.AppVersion = UniversalAnalyticsPlugin.Helpers.GetAppAttribute("Version"); 85 | 86 | _trackerStarted = true; 87 | 88 | Deployment.Current.Dispatcher.BeginInvoke(() => 89 | { 90 | Application.Current.UnhandledException += Analytics_UnhandledException; 91 | TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; 92 | }); 93 | 94 | DispatchCommandResult(new PluginResult(PluginResult.Status.OK, "Tracker started")); 95 | } 96 | else 97 | { 98 | DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Tracker id is not valid")); 99 | } 100 | } 101 | 102 | public override void OnResume(object sender, ActivatedEventArgs e) 103 | { 104 | if (_suspended.HasValue && SessionTimeout.HasValue) 105 | { 106 | var suspendedAgo = DateTime.UtcNow.Subtract(_suspended.Value); 107 | if (suspendedAgo > TimeSpan.FromSeconds((double)SessionTimeout)) 108 | { 109 | _tracker.SetStartSession(true); 110 | } 111 | } 112 | 113 | if (_trackerStarted && AutoAppLifetimeTracking) 114 | { 115 | _tracker.SendEvent("app", "resume", !e.IsApplicationInstancePreserved ? "tombstoned" : null, 0); 116 | } 117 | } 118 | 119 | public override void OnPause(object sender, DeactivatedEventArgs e) 120 | { 121 | if (_trackerStarted && AutoAppLifetimeTracking) 122 | { 123 | _tracker.SendEvent("app", "suspend", e.Reason.ToString(), 0); 124 | } 125 | 126 | _suspended = DateTime.UtcNow; 127 | } 128 | 129 | private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) 130 | { 131 | var ex = e.Exception.InnerException ?? e.Exception; // inner exception contains better info for unobserved tasks 132 | _tracker.SendException(ex.ToString(), false); 133 | } 134 | 135 | private void Analytics_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e) 136 | { 137 | _tracker.SendException(e.ExceptionObject.ToString(), true); 138 | 139 | if (Debugger.IsAttached) 140 | { 141 | // An unhandled exception has occurred; break into the debugger 142 | Debugger.Break(); 143 | } 144 | } 145 | 146 | public void setUserId(string options) 147 | { 148 | if (!_trackerStarted) 149 | { 150 | DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Tracker not started")); 151 | return; 152 | } 153 | 154 | string[] args = JsonHelper.Deserialize(options); 155 | string userId = null; 156 | 157 | if (args.Length > 0) userId = args[0]; 158 | 159 | _tracker.UserId = userId; 160 | DispatchCommandResult(new PluginResult(PluginResult.Status.OK, "Set user id: " + args[0])); 161 | } 162 | 163 | public void debugMode(string options) 164 | { 165 | _trackerManager.IsDebugEnabled = true; 166 | 167 | DispatchCommandResult(new PluginResult(PluginResult.Status.OK, "debugMode enabled")); 168 | } 169 | 170 | public void trackView(string options) 171 | { 172 | if (!_trackerStarted) 173 | { 174 | DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Tracker not started")); 175 | return; 176 | } 177 | 178 | string[] args = JsonHelper.Deserialize(options); 179 | 180 | if (args.Length > 0 && args[0] != null && args[0].Length > 0) 181 | { 182 | addCustomDimensionsToTracker(_tracker); 183 | _tracker.SendView(args[0]); 184 | DispatchCommandResult(new PluginResult(PluginResult.Status.OK, "Track Screen: " + args[0])); 185 | } 186 | else 187 | { 188 | DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Expected one non-empty string argument")); 189 | } 190 | } 191 | 192 | public void addCustomDimension(string options) 193 | { 194 | string[] args = JsonHelper.Deserialize(options); 195 | 196 | int index = 0; 197 | bool hasIndex = false; 198 | string value = null; 199 | 200 | if (args.Length > 0) hasIndex = int.TryParse(args[0], out index); 201 | if (args.Length > 1) value = args[1]; 202 | 203 | if (hasIndex && value != null) 204 | { 205 | // Remove the key if it already exists 206 | _customDimensions.Remove(index); 207 | _customDimensions.Add(index, value); 208 | DispatchCommandResult(new PluginResult(PluginResult.Status.OK, "Add Custom Dimension: " + index)); 209 | } 210 | else 211 | { 212 | DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Expected non-empty integer, string arguments")); 213 | } 214 | } 215 | 216 | public void trackEvent(string options) 217 | { 218 | if (!_trackerStarted) 219 | { 220 | DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Tracker not started")); 221 | return; 222 | } 223 | 224 | string[] args = JsonHelper.Deserialize(options); 225 | 226 | // Default values 227 | string category = null, action = null, label = null; 228 | long value = 0; 229 | 230 | if (args.Length > 0) category = args[0]; 231 | if (args.Length > 1) action = args[1]; 232 | if (args.Length > 2) label = args[2]; 233 | if (args.Length > 3) long.TryParse(args[3], out value); 234 | 235 | addCustomDimensionsToTracker(_tracker); 236 | _tracker.SendEvent(category, action, label, value); 237 | 238 | DispatchCommandResult(new PluginResult(PluginResult.Status.OK, "Track Event: " + category)); 239 | } 240 | 241 | public void trackException(string options) 242 | { 243 | if (!_trackerStarted) 244 | { 245 | DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Tracker not started")); 246 | return; 247 | } 248 | 249 | string[] args = JsonHelper.Deserialize(options); 250 | 251 | if (args.Length == 0 || args[0] == null || args[0].Length == 0) 252 | { 253 | DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Expected non-empty string arguments.")); 254 | return; 255 | } 256 | 257 | // Default values 258 | string description = null; 259 | bool isFatal = false; 260 | 261 | if (args.Length > 0) description = args[0]; 262 | if (args.Length > 1) bool.TryParse(args[1], out isFatal); 263 | 264 | addCustomDimensionsToTracker(_tracker); 265 | _tracker.SendException(description, isFatal); 266 | 267 | DispatchCommandResult(new PluginResult(PluginResult.Status.OK, "Track Exception: " + description)); 268 | } 269 | 270 | public void trackTiming(string options) 271 | { 272 | if (!_trackerStarted) 273 | { 274 | DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Tracker not started")); 275 | return; 276 | } 277 | 278 | string[] args = JsonHelper.Deserialize(options); 279 | 280 | if (args.Length == 0 || args[0] == null || args[0].Length == 0) 281 | { 282 | DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Expected non-empty string arguments.")); 283 | return; 284 | } 285 | 286 | // Default values 287 | string category = null, variable = null, label = null; 288 | long intervalInMs = 0; 289 | 290 | if (args.Length > 0) category = args[0]; 291 | if (args.Length > 1) long.TryParse(args[1], out intervalInMs); 292 | if (args.Length > 2) variable = args[2]; 293 | if (args.Length > 3) label = args[3]; 294 | 295 | addCustomDimensionsToTracker(_tracker); 296 | _tracker.SendTiming(TimeSpan.FromMilliseconds(intervalInMs), category, variable, label); 297 | 298 | DispatchCommandResult(new PluginResult(PluginResult.Status.OK, "Track Timing: " + category)); 299 | } 300 | 301 | public void addTransaction(string options) 302 | { 303 | if (!_trackerStarted) 304 | { 305 | DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Tracker not started")); 306 | return; 307 | } 308 | 309 | string[] args = JsonHelper.Deserialize(options); 310 | 311 | if (args.Length == 0 || args[0] == null || args[0].Length == 0) 312 | { 313 | DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Expected non-empty ID.")); 314 | return; 315 | } 316 | 317 | Transaction transaction = new Transaction(); 318 | 319 | // Default values 320 | double revenue = 0, tax = 0, shipping = 0; 321 | 322 | if (args.Length > 0) transaction.TransactionId = args[0]; 323 | if (args.Length > 1) transaction.Affiliation = args[1]; 324 | if (args.Length > 2) 325 | { 326 | double.TryParse(args[2], out revenue); 327 | transaction.TotalCostInMicros = (long)(revenue * 1000000); 328 | } 329 | if (args.Length > 3) 330 | { 331 | double.TryParse(args[3], out tax); 332 | transaction.TotalTaxInMicros = (long)(tax * 1000000); 333 | } 334 | if (args.Length > 4) 335 | { 336 | double.TryParse(args[4], out shipping); 337 | transaction.ShippingCostInMicros = (long)(shipping * 1000000); 338 | } 339 | if (args.Length > 5) transaction.CurrencyCode = args[5]; 340 | 341 | addCustomDimensionsToTracker(_tracker); 342 | _tracker.SendTransaction(transaction); 343 | 344 | DispatchCommandResult(new PluginResult(PluginResult.Status.OK, "Add Transaction: " + transaction.TransactionId)); 345 | } 346 | 347 | public void addTransactionItem(string options) 348 | { 349 | if (!_trackerStarted) 350 | { 351 | DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Tracker not started")); 352 | return; 353 | } 354 | 355 | string[] args = JsonHelper.Deserialize(options); 356 | 357 | if (args.Length == 0 || args[0] == null || args[0].Length == 0) 358 | { 359 | DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Expected non-empty ID.")); 360 | return; 361 | } 362 | 363 | TransactionItem transactionItem = new TransactionItem(); 364 | 365 | // Default values 366 | double price = 0; 367 | long quantity = 0; 368 | 369 | if (args.Length > 0) transactionItem.TransactionId = args[0]; 370 | if (args.Length > 1) transactionItem.Name = args[1]; 371 | if (args.Length > 2) transactionItem.SKU = args[2]; 372 | if (args.Length > 3) transactionItem.Category = args[3]; 373 | if (args.Length > 4) 374 | { 375 | double.TryParse(args[4], out price); 376 | transactionItem.PriceInMicros = (long)(price * 1000000); 377 | } 378 | if (args.Length > 5) 379 | { 380 | long.TryParse(args[5], out quantity); 381 | transactionItem.Quantity = quantity; 382 | } 383 | if (args.Length > 6) transactionItem.CurrencyCode = args[6]; 384 | 385 | addCustomDimensionsToTracker(_tracker); 386 | _tracker.SendTransactionItem(transactionItem); 387 | 388 | DispatchCommandResult(new PluginResult(PluginResult.Status.OK, "Add Transaction Item: " + transactionItem.TransactionId)); 389 | } 390 | 391 | private void addCustomDimensionsToTracker(Tracker tracker) 392 | { 393 | foreach (KeyValuePair dimension in _customDimensions) 394 | { 395 | tracker.SetCustomDimension(dimension.Key, dimension.Value); 396 | } 397 | } 398 | } 399 | } -------------------------------------------------------------------------------- /www/analytics.js: -------------------------------------------------------------------------------- 1 | function UniversalAnalyticsPlugin() {} 2 | 3 | UniversalAnalyticsPlugin.prototype.startTrackerWithId = function(id, dispatchPeriod, success, error) { 4 | if (typeof dispatchPeriod === 'undefined' || dispatchPeriod === null) { 5 | dispatchPeriod = 30; 6 | } else if (typeof dispatchPeriod === 'function' && typeof error === 'undefined') { 7 | // Called without dispatchPeriod but with a callback. 8 | // Looks like the original API was used so shift parameters over to remain compatible. 9 | error = success; 10 | success = dispatchPeriod; 11 | dispatchPeriod = 30; 12 | } 13 | cordova.exec(success, error, 'UniversalAnalytics', 'startTrackerWithId', [id, dispatchPeriod]); 14 | }; 15 | 16 | UniversalAnalyticsPlugin.prototype.setAllowIDFACollection = function(enable, success, error) { 17 | cordova.exec(success, error, 'UniversalAnalytics', 'setAllowIDFACollection', [enable]); 18 | }; 19 | 20 | UniversalAnalyticsPlugin.prototype.setUserId = function(id, success, error) { 21 | cordova.exec(success, error, 'UniversalAnalytics', 'setUserId', [id]); 22 | }; 23 | 24 | UniversalAnalyticsPlugin.prototype.setAnonymizeIp = function(anonymize, success, error) { 25 | cordova.exec(success, error, 'UniversalAnalytics', 'setAnonymizeIp', [anonymize]); 26 | }; 27 | 28 | UniversalAnalyticsPlugin.prototype.setOptOut = function(optout, success, error) { 29 | cordova.exec(success, error, 'UniversalAnalytics', 'setOptOut', [optout]); 30 | }; 31 | 32 | UniversalAnalyticsPlugin.prototype.setAppVersion = function(version, success, error) { 33 | cordova.exec(success, error, 'UniversalAnalytics', 'setAppVersion', [version]); 34 | }; 35 | 36 | UniversalAnalyticsPlugin.prototype.getVar = function(variable, success, error) { 37 | cordova.exec(success, error, 'UniversalAnalytics', 'getVar', [variable]); 38 | }; 39 | 40 | UniversalAnalyticsPlugin.prototype.setVar = function(variable, value, success, error) { 41 | cordova.exec(success, error, 'UniversalAnalytics', 'setVar', [variable, value]); 42 | }; 43 | 44 | UniversalAnalyticsPlugin.prototype.dispatch = function(success, error) { 45 | cordova.exec(success, error, 'UniversalAnalytics', 'dispatch', []); 46 | }; 47 | 48 | /* enables verbose logging */ 49 | UniversalAnalyticsPlugin.prototype.debugMode = function(success, error) { 50 | cordova.exec(success, error, 'UniversalAnalytics', 'debugMode', []); 51 | }; 52 | 53 | UniversalAnalyticsPlugin.prototype.trackMetric = function(key, value, success, error) { 54 | // as key was formerly documented to be of type string, 55 | // we need to at least accept string formatted numbers and pass the converted number 56 | var numberKey = key; 57 | if (typeof key === "string") { 58 | numberKey = Number.parseInt(key); 59 | if (isNaN(numberKey)) { 60 | throw Error("key must be a valid integer or string formatted integer"); 61 | } 62 | } 63 | 64 | // as value was formerly documented to be of type string 65 | // and therefore platform implementations expect value parameter of type string, 66 | // we need to cast the value parameter to string - although gathered metrics are infact number types. 67 | var stringValue = value || ""; 68 | if (typeof stringValue !== "string") { 69 | stringValue = String(value); 70 | } 71 | cordova.exec(success, error, 'UniversalAnalytics', 'trackMetric', [numberKey, stringValue]); 72 | }; 73 | 74 | UniversalAnalyticsPlugin.prototype.trackView = function(screen, campaignUrl, newSession, success, error) { 75 | if (typeof campaignUrl === 'undefined' || campaignUrl === null) { 76 | campaignUrl = ''; 77 | } 78 | 79 | if (typeof newSession === 'undefined' || newSession === null) { 80 | newSession = false; 81 | } 82 | 83 | cordova.exec(success, error, 'UniversalAnalytics', 'trackView', [screen, campaignUrl, newSession]); 84 | }; 85 | 86 | UniversalAnalyticsPlugin.prototype.addCustomDimension = function(key, value, success, error) { 87 | if (typeof key !== "number") { 88 | throw Error("key must be a valid integer not '" + typeof key + "'"); 89 | } 90 | cordova.exec(success, error, 'UniversalAnalytics', 'addCustomDimension', [key, value]); 91 | }; 92 | 93 | UniversalAnalyticsPlugin.prototype.trackEvent = function(category, action, label, value, newSession, success, error) { 94 | if (typeof label === 'undefined' || label === null) { 95 | label = ''; 96 | } 97 | if (typeof value === 'undefined' || value === null) { 98 | value = 0; 99 | } 100 | 101 | if (typeof newSession === 'undefined' || newSession === null) { 102 | newSession = false; 103 | } 104 | 105 | cordova.exec(success, error, 'UniversalAnalytics', 'trackEvent', [category, action, label, value, newSession]); 106 | }; 107 | 108 | /** 109 | * https://developers.google.com/analytics/devguides/collection/android/v3/exceptions 110 | */ 111 | UniversalAnalyticsPlugin.prototype.trackException = function(description, fatal, success, error) { 112 | cordova.exec(success, error, 'UniversalAnalytics', 'trackException', [description, fatal]); 113 | }; 114 | 115 | UniversalAnalyticsPlugin.prototype.trackTiming = function(category, intervalInMilliseconds, name, label, success, error) { 116 | if (typeof intervalInMilliseconds === 'undefined' || intervalInMilliseconds === null) { 117 | intervalInMilliseconds = 0; 118 | } 119 | if (typeof name === 'undefined' || name === null) { 120 | name = ''; 121 | } 122 | if (typeof label === 'undefined' || label === null) { 123 | label = ''; 124 | } 125 | 126 | cordova.exec(success, error, 'UniversalAnalytics', 'trackTiming', [category, intervalInMilliseconds, name, label]); 127 | }; 128 | 129 | /* Google Analytics e-Commerce Tracking */ 130 | /* https://developers.google.com/analytics/devguides/collection/analyticsjs/ecommerce */ 131 | UniversalAnalyticsPlugin.prototype.addTransaction = function(transactionId, affiliation, revenue, tax, shipping, currencyCode, success, error) { 132 | cordova.exec(success, error, 'UniversalAnalytics', 'addTransaction', [transactionId, affiliation, revenue, tax, shipping, currencyCode]); 133 | }; 134 | 135 | UniversalAnalyticsPlugin.prototype.addTransactionItem = function(transactionId, name ,sku, category, price, quantity, currencyCode, success, error) { 136 | cordova.exec(success, error, 'UniversalAnalytics', 'addTransactionItem', [transactionId, name ,sku, category, price, quantity, currencyCode]); 137 | }; 138 | 139 | /* automatic uncaught exception tracking */ 140 | UniversalAnalyticsPlugin.prototype.enableUncaughtExceptionReporting = function (enable, success, error) { 141 | cordova.exec(success, error, 'UniversalAnalytics', 'enableUncaughtExceptionReporting', [enable]); 142 | }; 143 | 144 | module.exports = new UniversalAnalyticsPlugin(); 145 | --------------------------------------------------------------------------------