├── .github └── workflows │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── CHANGELOG.md.meta ├── Examples.unitypackage ├── Examples.unitypackage.meta ├── LICENSE ├── LICENSE.meta ├── Mixpanel.meta ├── Mixpanel ├── Config.cs ├── Config.cs.meta ├── Controller.cs ├── Controller.cs.meta ├── Editor.meta ├── Editor │ ├── Mixpanel.Editor.asmdef │ ├── Mixpanel.Editor.asmdef.meta │ ├── MixpanelSettingsEditor.cs │ ├── MixpanelSettingsEditor.cs.meta │ ├── MixpanelSettingsInspector.cs │ └── MixpanelSettingsInspector.cs.meta ├── Extensions.cs ├── Extensions.cs.meta ├── IPreferences.cs ├── IPreferences.cs.meta ├── Log.cs ├── Log.cs.meta ├── Mixpanel.asmdef ├── Mixpanel.asmdef.meta ├── MixpanelAPI.cs ├── MixpanelAPI.cs.meta ├── MixpanelSettings.cs ├── MixpanelSettings.cs.meta ├── PlayerPreferences.cs ├── PlayerPreferences.cs.meta ├── Storage.cs ├── Storage.cs.meta ├── Value.cs ├── Value.cs.meta ├── Worker.cs └── Worker.cs.meta ├── README.md ├── README.md.meta ├── Tests.unitypackage ├── Tests.unitypackage.meta ├── package.json └── package.json.meta /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" # Push events to matching v*, i.e. v1.0, v20.15.10 7 | 8 | jobs: 9 | build: 10 | name: "🚀 Release" 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: "Check-out" 14 | uses: actions/checkout@v1 15 | - name: "Update Release CHANGELOG" 16 | id: update-release-changelog 17 | uses: heinrichreimer/github-changelog-generator-action@v2.2 18 | with: 19 | token: ${{ secrets.GITHUB_TOKEN }} 20 | onlyLastTag: true 21 | stripHeaders: false 22 | base: "CHANGELOG.md" 23 | headerLabel: "# Changelog" 24 | breakingLabel: '### Breaking' 25 | enhancementLabel: '### Enhancements' 26 | stripGeneratorNotice: true 27 | bugsLabel: '### Fixes' 28 | issues: false 29 | issuesWoLabels: false 30 | pullRequests: true 31 | prWoLabels: true 32 | author: false 33 | verbose: true 34 | - name: Commit CHANGELOG Changes 35 | run: | 36 | git add . 37 | git config user.name "zihe.jia" 38 | git config user.email "zihe.jia@mixpanel.com" 39 | git commit -m "Update CHANGELOG" 40 | - name: Push CHANGELOG changes 41 | uses: ad-m/github-push-action@v0.6.0 42 | with: 43 | github_token: ${{ secrets.GITHUB_TOKEN }} 44 | branch: master 45 | force: true 46 | - name: "Prepare for the Github Release" 47 | id: generate-release-changelog 48 | uses: heinrichreimer/github-changelog-generator-action@v2.2 49 | with: 50 | token: ${{ secrets.GITHUB_TOKEN }} 51 | output: "output.md" 52 | headerLabel: "# Changelog" 53 | onlyLastTag: true 54 | stripHeaders: false 55 | breakingLabel: '### Breaking' 56 | enhancementLabel: '### Enhancements' 57 | stripGeneratorNotice: true 58 | bugsLabel: '### Fixes' 59 | issues: false 60 | issuesWoLabels: false 61 | pullRequests: true 62 | prWoLabels: true 63 | author: false 64 | verbose: true 65 | - name: "🚀 Create GitHub Release" 66 | uses: actions/create-release@v1 67 | env: 68 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 69 | with: 70 | tag_name: ${{ github.ref }} 71 | release_name: Release ${{ github.ref }} 72 | body: ${{ steps.generate-release-changelog.outputs.changelog }} 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /[Ll]ibrary/ 2 | /[Ll]ogs/ 3 | /[Tt]emp/ 4 | /[Oo]bj/ 5 | /[Bb]uild/ 6 | /[Bb]uilds/ 7 | /[Aa]rt/ 8 | /Assets/AssetStoreTools* 9 | # Visual Studio 2015 cache directory 10 | /.vs/ 11 | # Autogenerated VS/MD/Consulo solution and project files 12 | ExportedObj/ 13 | .consulo/ 14 | *.csproj 15 | *.unityproj 16 | *.sln 17 | *.suo 18 | *.tmp 19 | *.user 20 | *.userprefs 21 | *.pidb 22 | *.booproj 23 | *.svd 24 | *.pdb 25 | # Unity3D generated meta files 26 | *.pidb.meta 27 | # Unity3D Generated File On Crash Reports 28 | sysinfo.txt 29 | # Builds 30 | *.apk 31 | # Custom 32 | .vscode/ 33 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2 | 3 | ## [v3.5.3](https://github.com/mixpanel/mixpanel-unity/tree/v3.5.3) (2024-09-25) 4 | 5 | ### Enhancements 6 | 7 | - Provide more details to Assert [\#180](https://github.com/mixpanel/mixpanel-unity/pull/180) 8 | 9 | ### Fixes 10 | 11 | - Fix incorrect condition for determining if all tracking data was deleted [\#179](https://github.com/mixpanel/mixpanel-unity/pull/179) 12 | 13 | # 14 | 15 | ## [v3.5.2](https://github.com/mixpanel/mixpanel-unity/tree/v3.5.2) (2024-05-12) 16 | 17 | ### Fixes 18 | 19 | - Fix the compile errors in the development build [\#178](https://github.com/mixpanel/mixpanel-unity/pull/178) 20 | 21 | ## [v3.5.1](https://github.com/mixpanel/mixpanel-unity/tree/v3.5.1) (2024-05-02) 22 | 23 | ### Fixes 24 | 25 | - Remove internal tracking [\#175](https://github.com/mixpanel/mixpanel-unity/pull/175) 26 | - fix "Type of conditional expression cannot be determined" [\#174](https://github.com/mixpanel/mixpanel-unity/pull/174) 27 | 28 | # 29 | 30 | ## [v3.5.0](https://github.com/mixpanel/mixpanel-unity/tree/v3.5.0) (2024-02-29) 31 | 32 | ### Enhancements 33 | 34 | - Add the UseIpAddressForGeolocation option for MixpanelSettings [\#170](https://github.com/mixpanel/mixpanel-unity/pull/170), thanks @qwe321qwe321qwe321 ! 35 | - Add onFlushComplete callback [\#168](https://github.com/mixpanel/mixpanel-unity/pull/168), thanks @elic-sightful 36 | 37 | ### Bug fixes 38 | - Use DEVELOPMENT_BUILD build flag instead of Debug.isDebugBuild [\#167](https://github.com/mixpanel/mixpanel-unity/pull/167), thanks @elic-sightful 39 | - Fixed single quotes in readme [\#172](https://github.com/mixpanel/mixpanel-unity/pull/172), thanks @AldeRoberge 40 | 41 | # 42 | 43 | ## [v3.4.2](https://github.com/mixpanel/mixpanel-unity/tree/v3.4.2) (2023-11-08) 44 | 45 | ### Bug fixes 46 | 47 | Remove the internal release script. https://github.com/mixpanel/mixpanel-unity/pull/165 48 | address issue #164 49 | 50 | # 51 | 52 | ## [v3.4.2](https://github.com/mixpanel/mixpanel-unity/tree/v3.4.2) (2023-09-20) 53 | 54 | ### Bug fixes 55 | 56 | Remove the internal release script. https://github.com/mixpanel/mixpanel-unity/pull/165 57 | address issue #164 58 | 59 | # 60 | 61 | ## [v3.4.1](https://github.com/mixpanel/mixpanel-unity/tree/v3.4.1) (2023-09-20) 62 | 63 | ### Enhancements 64 | 65 | - fix RegisterOnce not really register once [\#162](https://github.com/mixpanel/mixpanel-unity/pull/162) 66 | 67 | # 68 | 69 | ## [v3.4.0](https://github.com/mixpanel/mixpanel-unity/tree/v3.4.0) (2022-12-22) 70 | 71 | ### Enhancements 72 | 73 | - Allow changing settings at runtime [\#153](https://github.com/mixpanel/mixpanel-unity/pull/153) 74 | - Optimize PlayerPrefs event ID scanning when sending event batches [\#152](https://github.com/mixpanel/mixpanel-unity/pull/152) 75 | 76 | # 77 | 78 | ## [v3.3.2](https://github.com/mixpanel/mixpanel-unity/tree/v3.3.2) (2022-09-21) 79 | 80 | ### Fixes 81 | 82 | - dont use milliseconds for $duration of timed events [\#150](https://github.com/mixpanel/mixpanel-unity/pull/150) 83 | 84 | # 85 | 86 | ## [v3.3.1](https://github.com/mixpanel/mixpanel-unity/tree/v3.3.1) (2022-08-18) 87 | 88 | ### Fixes 89 | 90 | - LICENSE has no meta file [\#147](https://github.com/mixpanel/mixpanel-unity/issues/147) 91 | 92 | # 93 | 94 | ## [v3.3.0](https://github.com/mixpanel/mixpanel-unity/tree/v3.3.0) (2022-08-04) 95 | 96 | ### Enhancements 97 | 98 | - send millisecond precision timestamps [\#146](https://github.com/mixpanel/mixpanel-unity/pull/146) 99 | - Remove NPS survey and add more DevX tracking [\#144](https://github.com/mixpanel/mixpanel-unity/pull/144) 100 | 101 | # 102 | 103 | ## [v3.2.0](https://github.com/mixpanel/mixpanel-unity/tree/v3.2.0) (2022-04-30) 104 | 105 | ### Enhancements 106 | 107 | - Add method 'SetPreferencesSource' to Mixpanel [\#139](https://github.com/mixpanel/mixpanel-unity/pull/139) 108 | 109 | # 110 | 111 | ## [v3.1.1](https://github.com/mixpanel/mixpanel-unity/tree/v3.1.1) (2022-04-30) 112 | 113 | ### Enhancements 114 | 115 | - Add Debug tracking and Dev NPS Survey [\#138](https://github.com/mixpanel/mixpanel-unity/pull/138) 116 | 117 | ### Fixes 118 | 119 | - Remove push notification related stuff [\#137](https://github.com/mixpanel/mixpanel-unity/pull/137) 120 | 121 | # 122 | 123 | ## [v3.1.0](https://github.com/mixpanel/mixpanel-unity/tree/v3.1.0) (2022-04-21) 124 | 125 | ### Enhancements 126 | 127 | - Customize player preferences [\#128](https://github.com/mixpanel/mixpanel-unity/pull/128) 128 | 129 | ### Fixes 130 | 131 | - Fix compilation errors on platforms other than Android [\#135](https://github.com/mixpanel/mixpanel-unity/pull/135) 132 | 133 | # 134 | 135 | ## [v3.0.2](https://github.com/mixpanel/mixpanel-unity/tree/v3.0.2) (2022-01-14) 136 | 137 | ### Fixes 138 | 139 | - Fix the Mixpanel gameObject could potentially being destroyed when switch between scenes [\#125](https://github.com/mixpanel/mixpanel-unity/pull/125) 140 | 141 | # 142 | 143 | ## [v3.0.1](https://github.com/mixpanel/mixpanel-unity/tree/v3.0.1) (2021-11-11) 144 | 145 | **Closed issues:** 146 | 147 | - Error with Controller.cs with earlier unity versions \( 2019.4\) [\#120](https://github.com/mixpanel/mixpanel-unity/issues/120) 148 | 149 | **Merged pull requests:** 150 | 151 | - backward compatibility with earlier version than 2020.1 [\#121](https://github.com/mixpanel/mixpanel-unity/pull/121) 152 | 153 | # 154 | 155 | ## [v3.0.0](https://github.com/mixpanel/mixpanel-unity/tree/v3.0.0) (2021-10-21) 156 | 157 | ### Enhancements 158 | 159 | - Android Push Notifications [\#42](https://github.com/mixpanel/mixpanel-unity/issues/42) 160 | - Tracking persistence layer refactor [\#119](https://github.com/mixpanel/mixpanel-unity/pull/119) 161 | 162 | ### Fixes 163 | 164 | - `OnApplicationQuit` possible race condition with tracking [\#69](https://github.com/mixpanel/mixpanel-unity/issues/69) 165 | 166 | **Closed issues:** 167 | 168 | - Expose Mixpanel.ClearSuperProperties? [\#118](https://github.com/mixpanel/mixpanel-unity/issues/118) 169 | - Mixpanel doesn't work with IL2CPP build on windows. [\#117](https://github.com/mixpanel/mixpanel-unity/issues/117) 170 | - TrackCharge doc is out of date? [\#116](https://github.com/mixpanel/mixpanel-unity/issues/116) 171 | - IL2CPP error building for Android in Unity 2020.2.2f1 [\#112](https://github.com/mixpanel/mixpanel-unity/issues/112) 172 | - v2.2.2 still referencing IDFA [\#111](https://github.com/mixpanel/mixpanel-unity/issues/111) 173 | - Mixpanel prevents opening the same Unity project open twice [\#110](https://github.com/mixpanel/mixpanel-unity/issues/110) 174 | - Standalone builds\(IL2CPP\) won't send Mixpanel events [\#108](https://github.com/mixpanel/mixpanel-unity/issues/108) 175 | - \[Mixpanel\] There was an error sending the request. System.AggregateException: One or more errors occurred. [\#106](https://github.com/mixpanel/mixpanel-unity/issues/106) 176 | - \[Mixpanel\] System.NotSupportedException: linked away [\#105](https://github.com/mixpanel/mixpanel-unity/issues/105) 177 | - WebGL freezing on v2.1.0 [\#90](https://github.com/mixpanel/mixpanel-unity/issues/90) 178 | - NullReferenceException on low performance devices [\#89](https://github.com/mixpanel/mixpanel-unity/issues/89) 179 | - JSON parse error on empty object [\#87](https://github.com/mixpanel/mixpanel-unity/issues/87) 180 | - End of file reached while trying to read queue item on UWP [\#85](https://github.com/mixpanel/mixpanel-unity/issues/85) 181 | 182 | **Merged pull requests:** 183 | 184 | - Improve README for quick start guide [\#115](https://github.com/mixpanel/mixpanel-unity/pull/115) 185 | - Add github workflow for auto release [\#114](https://github.com/mixpanel/mixpanel-unity/pull/114) 186 | 187 | ## [v2.2.3](https://github.com/mixpanel/mixpanel-unity/releases/tag/v2.2.3) 188 | ### Mar 8 - 2020 189 | ## Fixes 190 | - Remove `$ios_ifa` 191 | 192 | --- 193 | 194 | ## [v2.2.2](https://github.com/mixpanel/mixpanel-unity/releases/tag/v2.2.2) 195 | ### October 26 - 2020 196 | 197 | ## Fixes 198 | - Fix in some rare cases, event payload being sent incorrectly formatted or with changed values 199 | 200 | --- 201 | 202 | ## [v2.2.1](https://github.com/mixpanel/mixpanel-unity/releases/tag/v2.2.1) 203 | ### July 31st - 2020 204 | 205 | - Remove `$ios_ifa` user property for iOS devices: iOS 14 will not allow to read the IDFA value without permission. 206 | 207 | ## Fixes 208 | - Improve objects re-utilization. 209 | 210 | --- 211 | 212 | ## [v2.2.0](https://github.com/mixpanel/mixpanel-unity/releases/tag/v2.2.0) 213 | ### June 2nd - 2020 214 | 215 | ## Features 216 | - You can now manually initialize the library. You first need to enable this setting 217 | from your Project Settings menu. To use the library, call `Mixpanel.Init()` before you interact with it 218 | and `Mixpanel.Disable()` to dispose the component. 219 | 220 | ## Fixes 221 | - Fix fatal errror in `Mixpanel.Reset()` at app boot (thanks @RedHatJef!) 222 | 223 | --- 224 | 225 | ## [v2.1.4](https://github.com/mixpanel/mixpanel-unity/releases/tag/v2.1.4) 226 | ### February 18th - 2020 227 | 228 | ## Fixes 229 | - Performance improvements. 230 | - Fix set `PushDeviceToken` for Android where an string is used. 231 | 232 | --- 233 | 234 | ## [v2.1.3](https://github.com/mixpanel/mixpanel-unity/releases/tag/v2.1.3) 235 | ### February 10th - 2020 236 | 237 | ## Fixes 238 | - Remove `ClearCharges` from `OptOutTracking` to avoid having orphan profiles at mixpanel 239 | 240 | --- 241 | 242 | ## [v2.1.2](https://github.com/mixpanel/mixpanel-unity/releases/tag/v2.1.2) 243 | ### January 9th - 2020 244 | 245 | ## New features 246 | - Add `SetToken()` method to set project token programatically 247 | 248 | --- 249 | 250 | ## [v2.1.1](https://github.com/mixpanel/mixpanel-unity/releases/tag/v2.1.1) 251 | ### December 17th - 2019 252 | 253 | ## Fixes 254 | - Added support for older Unity versions if .NET 4.x equivalent is the selected scripting runtime version 255 | - Fix value serialize/deserialize bug (#93) 256 | 257 | --- 258 | 259 | ## [v2.1.0](https://github.com/mixpanel/mixpanel-unity/releases/tag/v2.1.0) 260 | ### November 14th - 2019 261 | 262 | ## Fixes 263 | - API Error: Invalid JSON Encoding for numbers (https://github.com/mixpanel/mixpanel-unity/issues/74) 264 | - Default people properties not been set properly 265 | - `PushDeviceToken` not working (https://github.com/mixpanel/mixpanel-unity/issues/73) 266 | - JSON encoding of special characters like `\"` or `\t`, etc... 267 | - A flush operation now sends everything that happened until just right before the API is called. 268 | - Properly migrate state from SDK 1.X to 2.X to preserve super properties and distinct ids. 269 | - Major performance improvements 270 | 271 | ## New features 272 | - Added de-duplication logic to prevent duplicated events to exist in your project 273 | - Added an integration event 274 | - Added new default event and people properties 275 | 276 | --- 277 | 278 | ## [v2.0.0](https://github.com/mixpanel/mixpanel-unity/releases/tag/v2.0.0) 279 | ### September 24th - 2019 280 | 281 | #### This version is a complete rewrite of the library to support ALL platforms that unity can compile to. 282 | 283 | The basis for this rewrite was https://github.com/mixpanel/mixpanel-unity/issues/10 to support WebGL but since the library was rewriten in plain c# it should work for any platform unity can compile to. 284 | 285 | The API has stayed compliant with the documentation though there maybe a few changes to a few of the mixpanel properties that come though automatically due to unity not having access to certain system/device information easily please reachout to us if there is something missing after you upgrade and we can introspect it 286 | 287 | The github repo has also been structured so that it supports the Unity 2018.4 package manager (please see the README for package manager install instructions) 288 | 289 | This version of the library should support backwards compatibility with Unity 2018.x but it has only been tested with the 2018 LTS release. 290 | 291 | --- 292 | 293 | ## [v1.1.1](https://github.com/mixpanel/mixpanel-unity/releases/tag/v1.1.1) 294 | ### December 18th - 2017 295 | 296 | Bug fixes 297 | 298 | --- 299 | 300 | ## [v1.1.0](https://github.com/mixpanel/mixpanel-unity/releases/tag/v1.1.0) 301 | ### October 6th - 2017 302 | 303 | Improvements 304 | Persist alias and protect users from identifying the user as their alias 305 | Reset distinct_id and alias when reset is called 306 | Clean ups 307 | Fixes 308 | Switching platforms could lead to MixpanelPostProcessor been executed at the wrong time 309 | Reversed the attribution of app build number and version string ($app_build_number and $app_version_string) 310 | Fix crash occurring only for Android when AdvertisingIdClient.Info.getId() was returning null. 311 | 312 | --- 313 | 314 | ## [v1.0.1](https://github.com/mixpanel/mixpanel-unity/releases/tag/1.0.1) 315 | ### June 16th - 2016 316 | 317 | iOS 318 | Added optional support for advertisingIdentifier 319 | Added support for Bitcode 320 | Windows 321 | Added missing dependency for x86 and x86_64 322 | All Platforms 323 | Networking now respects the HTTP Retry-After header 324 | Networking now backs off exponentially on failure 325 | 326 | --- 327 | 328 | ## [v1.0.0](https://github.com/mixpanel/mixpanel-unity/releases/tag/1.0.0) 329 | ### June 3rd - 2016 330 | 331 | We are thrilled to release the official Mixpanel Unity SDK. Some links to get started: 332 | 333 | * [Official documentation](https://mixpanel.com/help/reference/unity) 334 | * [Full API Reference](http://mixpanel.github.io/mixpanel-unity/api-reference/annotated.html) 335 | * [Sample application](https://github.com/mixpanel/mixpanel-unity/tree/master/deployments/UnityMixpanel/Assets/Mixpanel/Sample) 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | -------------------------------------------------------------------------------- /CHANGELOG.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 057073b6cc6e7fd4c89e14722a33b4d6 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Examples.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-unity/aa28e6eeefe01ef98c6ca53b2551394bc6660ec2/Examples.unitypackage -------------------------------------------------------------------------------- /Examples.unitypackage.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 88f9f2404af6c7848990f3cfb3a90d79 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Mixpanel, Inc. 2 | 3 | 4 | Apache License 5 | Version 2.0, January 2004 6 | https://www.apache.org/licenses/ 7 | 8 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 9 | 10 | 1. Definitions. 11 | 12 | "License" shall mean the terms and conditions for use, reproduction, 13 | and distribution as defined by Sections 1 through 9 of this document. 14 | 15 | "Licensor" shall mean the copyright owner or entity authorized by 16 | the copyright owner that is granting the License. 17 | 18 | "Legal Entity" shall mean the union of the acting entity and all 19 | other entities that control, are controlled by, or are under common 20 | control with that entity. For the purposes of this definition, 21 | "control" means (i) the power, direct or indirect, to cause the 22 | direction or management of such entity, whether by contract or 23 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 24 | outstanding shares, or (iii) beneficial ownership of such entity. 25 | 26 | "You" (or "Your") shall mean an individual or Legal Entity 27 | exercising permissions granted by this License. 28 | 29 | "Source" form shall mean the preferred form for making modifications, 30 | including but not limited to software source code, documentation 31 | source, and configuration files. 32 | 33 | "Object" form shall mean any form resulting from mechanical 34 | transformation or translation of a Source form, including but 35 | not limited to compiled object code, generated documentation, 36 | and conversions to other media types. 37 | 38 | "Work" shall mean the work of authorship, whether in Source or 39 | Object form, made available under the License, as indicated by a 40 | copyright notice that is included in or attached to the work 41 | (an example is provided in the Appendix below). 42 | 43 | "Derivative Works" shall mean any work, whether in Source or Object 44 | form, that is based on (or derived from) the Work and for which the 45 | editorial revisions, annotations, elaborations, or other modifications 46 | represent, as a whole, an original work of authorship. For the purposes 47 | of this License, Derivative Works shall not include works that remain 48 | separable from, or merely link (or bind by name) to the interfaces of, 49 | the Work and Derivative Works thereof. 50 | 51 | "Contribution" shall mean any work of authorship, including 52 | the original version of the Work and any modifications or additions 53 | to that Work or Derivative Works thereof, that is intentionally 54 | submitted to Licensor for inclusion in the Work by the copyright owner 55 | or by an individual or Legal Entity authorized to submit on behalf of 56 | the copyright owner. For the purposes of this definition, "submitted" 57 | means any form of electronic, verbal, or written communication sent 58 | to the Licensor or its representatives, including but not limited to 59 | communication on electronic mailing lists, source code control systems, 60 | and issue tracking systems that are managed by, or on behalf of, the 61 | Licensor for the purpose of discussing and improving the Work, but 62 | excluding communication that is conspicuously marked or otherwise 63 | designated in writing by the copyright owner as "Not a Contribution." 64 | 65 | "Contributor" shall mean Licensor and any individual or Legal Entity 66 | on behalf of whom a Contribution has been received by Licensor and 67 | subsequently incorporated within the Work. 68 | 69 | 2. Grant of Copyright License. Subject to the terms and conditions of 70 | this License, each Contributor hereby grants to You a perpetual, 71 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 72 | copyright license to reproduce, prepare Derivative Works of, 73 | publicly display, publicly perform, sublicense, and distribute the 74 | Work and such Derivative Works in Source or Object form. 75 | 76 | 3. Grant of Patent License. Subject to the terms and conditions of 77 | this License, each Contributor hereby grants to You a perpetual, 78 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 79 | (except as stated in this section) patent license to make, have made, 80 | use, offer to sell, sell, import, and otherwise transfer the Work, 81 | where such license applies only to those patent claims licensable 82 | by such Contributor that are necessarily infringed by their 83 | Contribution(s) alone or by combination of their Contribution(s) 84 | with the Work to which such Contribution(s) was submitted. If You 85 | institute patent litigation against any entity (including a 86 | cross-claim or counterclaim in a lawsuit) alleging that the Work 87 | or a Contribution incorporated within the Work constitutes direct 88 | or contributory patent infringement, then any patent licenses 89 | granted to You under this License for that Work shall terminate 90 | as of the date such litigation is filed. 91 | 92 | 4. Redistribution. You may reproduce and distribute copies of the 93 | Work or Derivative Works thereof in any medium, with or without 94 | modifications, and in Source or Object form, provided that You 95 | meet the following conditions: 96 | 97 | (a) You must give any other recipients of the Work or 98 | Derivative Works a copy of this License; and 99 | 100 | (b) You must cause any modified files to carry prominent notices 101 | stating that You changed the files; and 102 | 103 | (c) You must retain, in the Source form of any Derivative Works 104 | that You distribute, all copyright, patent, trademark, and 105 | attribution notices from the Source form of the Work, 106 | excluding those notices that do not pertain to any part of 107 | the Derivative Works; and 108 | 109 | (d) If the Work includes a "NOTICE" text file as part of its 110 | distribution, then any Derivative Works that You distribute must 111 | include a readable copy of the attribution notices contained 112 | within such NOTICE file, excluding those notices that do not 113 | pertain to any part of the Derivative Works, in at least one 114 | of the following places: within a NOTICE text file distributed 115 | as part of the Derivative Works; within the Source form or 116 | documentation, if provided along with the Derivative Works; or, 117 | within a display generated by the Derivative Works, if and 118 | wherever such third-party notices normally appear. The contents 119 | of the NOTICE file are for informational purposes only and 120 | do not modify the License. You may add Your own attribution 121 | notices within Derivative Works that You distribute, alongside 122 | or as an addendum to the NOTICE text from the Work, provided 123 | that such additional attribution notices cannot be construed 124 | as modifying the License. 125 | 126 | You may add Your own copyright statement to Your modifications and 127 | may provide additional or different license terms and conditions 128 | for use, reproduction, or distribution of Your modifications, or 129 | for any such Derivative Works as a whole, provided Your use, 130 | reproduction, and distribution of the Work otherwise complies with 131 | the conditions stated in this License. 132 | 133 | 5. Submission of Contributions. Unless You explicitly state otherwise, 134 | any Contribution intentionally submitted for inclusion in the Work 135 | by You to the Licensor shall be under the terms and conditions of 136 | this License, without any additional terms or conditions. 137 | Notwithstanding the above, nothing herein shall supersede or modify 138 | the terms of any separate license agreement you may have executed 139 | with Licensor regarding such Contributions. 140 | 141 | 6. Trademarks. This License does not grant permission to use the trade 142 | names, trademarks, service marks, or product names of the Licensor, 143 | except as required for reasonable and customary use in describing the 144 | origin of the Work and reproducing the content of the NOTICE file. 145 | 146 | 7. Disclaimer of Warranty. Unless required by applicable law or 147 | agreed to in writing, Licensor provides the Work (and each 148 | Contributor provides its Contributions) on an "AS IS" BASIS, 149 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 150 | implied, including, without limitation, any warranties or conditions 151 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 152 | PARTICULAR PURPOSE. You are solely responsible for determining the 153 | appropriateness of using or redistributing the Work and assume any 154 | risks associated with Your exercise of permissions under this License. 155 | 156 | 8. Limitation of Liability. In no event and under no legal theory, 157 | whether in tort (including negligence), contract, or otherwise, 158 | unless required by applicable law (such as deliberate and grossly 159 | negligent acts) or agreed to in writing, shall any Contributor be 160 | liable to You for damages, including any direct, indirect, special, 161 | incidental, or consequential damages of any character arising as a 162 | result of this License or out of the use or inability to use the 163 | Work (including but not limited to damages for loss of goodwill, 164 | work stoppage, computer failure or malfunction, or any and all 165 | other commercial damages or losses), even if such Contributor 166 | has been advised of the possibility of such damages. 167 | 168 | 9. Accepting Warranty or Additional Liability. While redistributing 169 | the Work or Derivative Works thereof, You may choose to offer, 170 | and charge a fee for, acceptance of support, warranty, indemnity, 171 | or other liability obligations and/or rights consistent with this 172 | License. However, in accepting such obligations, You may act only 173 | on Your own behalf and on Your sole responsibility, not on behalf 174 | of any other Contributor, and only if You agree to indemnify, 175 | defend, and hold each Contributor harmless for any liability 176 | incurred by, or claims asserted against, such Contributor by reason 177 | of your accepting any such warranty or additional liability. 178 | 179 | END OF TERMS AND CONDITIONS 180 | -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3edd57a7292eea349a30a483b8f0cc91 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Mixpanel.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d174630af6ce568459eb73167d424642 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Mixpanel/Config.cs: -------------------------------------------------------------------------------- 1 | namespace mixpanel 2 | { 3 | internal static class Config 4 | { 5 | // Can be overriden by MixpanelSettings 6 | internal static string TrackUrl = "https://api.mixpanel.com/track/?ip=1"; 7 | internal static string EngageUrl = "https://api.mixpanel.com/engage/?ip=1"; 8 | internal static bool ShowDebug = false; 9 | internal static bool ManualInitialization = false; 10 | internal static float FlushInterval = 60f; 11 | 12 | internal static int BatchSize = 50; 13 | 14 | internal const int PoolFillFrames = 50; 15 | internal const int PoolFillEachFrame = 20; 16 | } 17 | } -------------------------------------------------------------------------------- /Mixpanel/Config.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dc21234999e0f4181a02663c209cb57c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Mixpanel/Controller.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using UnityEngine; 6 | using UnityEngine.Networking; 7 | using UnityEngine.Profiling; 8 | using System.Threading; 9 | using Unity.Jobs; 10 | using Unity.Collections; 11 | using System.Net; 12 | using System.Net.Http; 13 | 14 | #if UNITY_IOS 15 | using UnityEngine.iOS; 16 | #endif 17 | 18 | namespace mixpanel 19 | { 20 | internal class Controller : MonoBehaviour 21 | { 22 | private static Value _autoTrackProperties; 23 | private static Value _autoEngageProperties; 24 | 25 | private static int _retryCount = 0; 26 | private static DateTime _retryTime; 27 | 28 | #region Singleton 29 | 30 | private static Controller _instance; 31 | 32 | [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] 33 | private static void InitializeBeforeSceneLoad() 34 | { 35 | MixpanelSettings.LoadSettings(); 36 | if (Config.ManualInitialization) return; 37 | Initialize(); 38 | } 39 | 40 | [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] 41 | private static void InitializeAfterSceneLoad() 42 | { 43 | if (Config.ManualInitialization) return; 44 | GetEngageDefaultProperties(); 45 | GetEventsDefaultProperties(); 46 | } 47 | 48 | internal static void Initialize() { 49 | // Copy over any runtime changes that happened before initialization from settings instance to the config. 50 | MixpanelSettings.Instance.ApplyToConfig(); 51 | GetInstance(); 52 | } 53 | 54 | internal static bool IsInitialized() { 55 | return _instance != null; 56 | } 57 | 58 | internal static void Disable() { 59 | if (_instance != null) { 60 | Destroy(_instance); 61 | } 62 | } 63 | 64 | internal static Controller GetInstance() 65 | { 66 | if (_instance == null) 67 | { 68 | GameObject g = new GameObject ("Mixpanel"); 69 | _instance = g.AddComponent(); 70 | DontDestroyOnLoad(g); 71 | } 72 | return _instance; 73 | } 74 | 75 | #endregion 76 | 77 | void OnDestroy() 78 | { 79 | Mixpanel.Log($"Mixpanel Component Destroyed"); 80 | } 81 | 82 | void OnApplicationPause(bool pauseStatus) 83 | { 84 | if (!pauseStatus) 85 | { 86 | Metadata.InitSession(); 87 | } 88 | } 89 | 90 | private void Start() 91 | { 92 | MigrateFrom1To2(); 93 | Mixpanel.Log($"Mixpanel Component Started"); 94 | StartCoroutine(WaitAndFlush()); 95 | } 96 | 97 | 98 | private IEnumerator WaitAndFlush() 99 | { 100 | while (true) 101 | { 102 | yield return new WaitForSecondsRealtime(Config.FlushInterval); 103 | DoFlush(); 104 | } 105 | } 106 | 107 | internal void DoFlush(Action onFlushComplete = null) 108 | { 109 | int coroutinesCount = 2; // Number of coroutines to wait for 110 | bool overallSuccess = true; 111 | 112 | Action onComplete = onFlushComplete != null ? 113 | new Action(success => { 114 | overallSuccess &= success; 115 | CheckCompletion(onFlushComplete, ref coroutinesCount, overallSuccess); 116 | }) 117 | : (Action)null; 118 | StartCoroutine(SendData(MixpanelStorage.FlushType.EVENTS, onComplete)); 119 | StartCoroutine(SendData(MixpanelStorage.FlushType.PEOPLE, onComplete)); 120 | } 121 | 122 | private IEnumerator SendData(MixpanelStorage.FlushType flushType, Action onComplete) 123 | { 124 | if (_retryTime > DateTime.Now && _retryCount > 0) 125 | { 126 | onComplete?.Invoke(false); 127 | yield break; 128 | } 129 | 130 | string url = (flushType == MixpanelStorage.FlushType.EVENTS) ? Config.TrackUrl : Config.EngageUrl; 131 | Value batch = MixpanelStorage.DequeueBatchTrackingData(flushType, Config.BatchSize); 132 | while (batch.Count > 0) { 133 | WWWForm form = new WWWForm(); 134 | String payload = batch.ToString(); 135 | form.AddField("data", payload); 136 | Mixpanel.Log("Sending batch of data: " + payload); 137 | using (UnityWebRequest request = UnityWebRequest.Post(url, form)) 138 | { 139 | yield return request.SendWebRequest(); 140 | #if UNITY_2020_1_OR_NEWER 141 | if (request.result != UnityWebRequest.Result.Success) 142 | #else 143 | if (request.isHttpError || request.isNetworkError) 144 | #endif 145 | { 146 | Mixpanel.Log("API request to " + url + "has failed with reason " + request.error); 147 | _retryCount += 1; 148 | double retryIn = Math.Pow(2, _retryCount - 1) * 60; 149 | retryIn = Math.Min(retryIn, 10 * 60); // limit 10 min 150 | _retryTime = DateTime.Now; 151 | _retryTime = _retryTime.AddSeconds(retryIn); 152 | Mixpanel.Log("Retrying request in " + retryIn + " seconds (retryCount=" + _retryCount + ")"); 153 | onComplete?.Invoke(false); 154 | yield break; 155 | } 156 | else 157 | { 158 | _retryCount = 0; 159 | MixpanelStorage.DeleteBatchTrackingData(batch); 160 | batch = MixpanelStorage.DequeueBatchTrackingData(flushType, Config.BatchSize); 161 | Mixpanel.Log("Successfully posted to " + url); 162 | } 163 | } 164 | } 165 | 166 | onComplete?.Invoke(true); 167 | } 168 | 169 | private void CheckCompletion(Action onFlushComplete, ref int coroutinesCount, bool overallSuccess) 170 | { 171 | // Decrease the counter 172 | coroutinesCount--; 173 | 174 | // If all coroutines are finished, invoke the onFlushComplete callback 175 | if (coroutinesCount == 0) 176 | { 177 | onFlushComplete?.Invoke(overallSuccess); 178 | } 179 | } 180 | 181 | private IEnumerator SendHttpEvent(string eventName, string apiToken, string distinctId, string properties, bool updatePeople) 182 | { 183 | string body = "{\"event\":\"" + eventName + "\",\"properties\":{\"token\":\"" + 184 | apiToken + "\",\"DevX\":true,\"mp_lib\":\"unity\"," + 185 | "\"$lib_version\":\"" + Mixpanel.MixpanelUnityVersion + "\"," + 186 | "\"Project Token\":\"" + distinctId + "\",\"distinct_id\":\"" + distinctId + "\"" + properties + "}}"; 187 | string payload = Convert.ToBase64String(Encoding.UTF8.GetBytes(body)); 188 | WWWForm form = new WWWForm(); 189 | form.AddField("data", payload); 190 | 191 | using (UnityWebRequest request = UnityWebRequest.Post(Config.TrackUrl, form)) { 192 | yield return request.SendWebRequest(); 193 | } 194 | 195 | if (updatePeople) { 196 | body = "{\"$add\":" + "{\"" + eventName + 197 | "\":1}," + 198 | "\"$token\":\"" + apiToken + "\"," + 199 | "\"$distinct_id\":\"" + distinctId + "\"}"; 200 | payload = Convert.ToBase64String(Encoding.UTF8.GetBytes(body)); 201 | form = new WWWForm(); 202 | form.AddField("data", payload); 203 | 204 | using (UnityWebRequest request = UnityWebRequest.Post(Config.EngageUrl, form)) { 205 | yield return request.SendWebRequest(); 206 | } 207 | } 208 | } 209 | 210 | #region InternalSDK 211 | 212 | private void MigrateFrom1To2() { 213 | if (!MixpanelStorage.HasMigratedFrom1To2) 214 | { 215 | string stateFile = Application.persistentDataPath + "/mp_state.json"; 216 | try 217 | { 218 | if (System.IO.File.Exists(stateFile)) 219 | { 220 | string state = System.IO.File.ReadAllText(stateFile); 221 | Value stateValue = Value.Deserialize(state); 222 | string distinctIdKey = "distinct_id"; 223 | if (stateValue.ContainsKey(distinctIdKey) && !stateValue[distinctIdKey].IsNull) 224 | { 225 | string distinctId = stateValue[distinctIdKey]; 226 | MixpanelStorage.DistinctId = distinctId; 227 | } 228 | string optedOutKey = "opted_out"; 229 | if (stateValue.ContainsKey(optedOutKey) && !stateValue[optedOutKey].IsNull) 230 | { 231 | bool optedOut = stateValue[optedOutKey]; 232 | MixpanelStorage.IsTracking = !optedOut; 233 | } 234 | } 235 | } 236 | catch (Exception) 237 | { 238 | Mixpanel.LogError("Error migrating state from v1 to v2"); 239 | } 240 | finally 241 | { 242 | System.IO.File.Delete(stateFile); 243 | } 244 | 245 | string superPropertiesFile = Application.persistentDataPath + "/mp_super_properties.json"; 246 | try 247 | { 248 | if (System.IO.File.Exists(superPropertiesFile)) 249 | { 250 | string superProperties = System.IO.File.ReadAllText(superPropertiesFile); 251 | Value superPropertiesValue = Value.Deserialize(superProperties); 252 | foreach (KeyValuePair kvp in superPropertiesValue) 253 | { 254 | if (!kvp.Key.StartsWith("$")) 255 | { 256 | Mixpanel.Register(kvp.Key, kvp.Value); 257 | } 258 | } 259 | } 260 | } 261 | catch (Exception) 262 | { 263 | Mixpanel.LogError("Error migrating super properties from v1 to v2"); 264 | } 265 | finally 266 | { 267 | System.IO.File.Delete(superPropertiesFile); 268 | } 269 | 270 | MixpanelStorage.HasMigratedFrom1To2 = true; 271 | } 272 | } 273 | 274 | internal static Value GetEngageDefaultProperties() { 275 | if (_autoEngageProperties == null) { 276 | Value properties = new Value(); 277 | #if UNITY_IOS 278 | properties["$ios_lib_version"] = Mixpanel.MixpanelUnityVersion; 279 | properties["$ios_version"] = Device.systemVersion; 280 | properties["$ios_app_release"] = Application.version; 281 | properties["$ios_device_model"] = SystemInfo.deviceModel; 282 | #elif UNITY_ANDROID 283 | properties["$android_lib_version"] = Mixpanel.MixpanelUnityVersion; 284 | properties["$android_os"] = "Android"; 285 | properties["$android_os_version"] = SystemInfo.operatingSystem; 286 | properties["$android_model"] = SystemInfo.deviceModel; 287 | properties["$android_app_version"] = Application.version; 288 | #else 289 | properties["$lib_version"] = Mixpanel.MixpanelUnityVersion; 290 | #endif 291 | _autoEngageProperties = properties; 292 | } 293 | return _autoEngageProperties; 294 | } 295 | 296 | private static Value GetEventsDefaultProperties() 297 | { 298 | if (_autoTrackProperties == null) { 299 | Value properties = new Value 300 | { 301 | {"mp_lib", "unity"}, 302 | {"$lib_version", Mixpanel.MixpanelUnityVersion}, 303 | {"$os", SystemInfo.operatingSystemFamily.ToString()}, 304 | {"$os_version", SystemInfo.operatingSystem}, 305 | {"$model", SystemInfo.deviceModel}, 306 | {"$app_version_string", Application.version}, 307 | {"$wifi", Application.internetReachability == NetworkReachability.ReachableViaLocalAreaNetwork}, 308 | {"$radio", Util.GetRadio()}, 309 | {"$device", Application.platform.ToString()}, 310 | {"$screen_dpi", Screen.dpi}, 311 | }; 312 | #if UNITY_IOS 313 | properties["$os"] = "Apple"; 314 | properties["$os_version"] = Device.systemVersion; 315 | properties["$manufacturer"] = "Apple"; 316 | #endif 317 | #if UNITY_ANDROID 318 | properties["$os"] = "Android"; 319 | #endif 320 | _autoTrackProperties = properties; 321 | } 322 | return _autoTrackProperties; 323 | } 324 | 325 | internal static void DoTrack(string eventName, Value properties) 326 | { 327 | if (!MixpanelStorage.IsTracking) return; 328 | if (properties == null) properties = new Value(); 329 | properties.Merge(GetEventsDefaultProperties()); 330 | // These auto properties can change in runtime so we don't bake them into AutoProperties 331 | properties["$screen_width"] = Screen.width; 332 | properties["$screen_height"] = Screen.height; 333 | properties.Merge(MixpanelStorage.OnceProperties); 334 | properties.Merge(MixpanelStorage.SuperProperties); 335 | Value startTime; 336 | if (MixpanelStorage.TimedEvents.TryGetValue(eventName, out startTime)) 337 | { 338 | properties["$duration"] = Util.CurrentTimeInSeconds() - (double)startTime; 339 | MixpanelStorage.TimedEvents.Remove(eventName); 340 | } 341 | properties["token"] = MixpanelSettings.Instance.Token; 342 | properties["distinct_id"] = MixpanelStorage.DistinctId; 343 | properties["time"] = Util.CurrentTimeInMilliseconds(); 344 | 345 | Value data = new Value(); 346 | 347 | data["event"] = eventName; 348 | data["properties"] = properties; 349 | data["$mp_metadata"] = Metadata.GetEventMetadata(); 350 | 351 | MixpanelStorage.EnqueueTrackingData(data, MixpanelStorage.FlushType.EVENTS); 352 | } 353 | 354 | internal static void DoEngage(Value properties) 355 | { 356 | if (!MixpanelStorage.IsTracking) return; 357 | properties["$token"] = MixpanelSettings.Instance.Token; 358 | properties["$distinct_id"] = MixpanelStorage.DistinctId; 359 | properties["$time"] = Util.CurrentTimeInMilliseconds(); 360 | properties["$mp_metadata"] = Metadata.GetPeopleMetadata(); 361 | 362 | MixpanelStorage.EnqueueTrackingData(properties, MixpanelStorage.FlushType.PEOPLE); 363 | } 364 | 365 | internal static void DoClear() 366 | { 367 | MixpanelStorage.DeleteAllTrackingData(MixpanelStorage.FlushType.EVENTS); 368 | MixpanelStorage.DeleteAllTrackingData(MixpanelStorage.FlushType.PEOPLE); 369 | } 370 | 371 | #endregion 372 | 373 | internal static class Metadata 374 | { 375 | private static Int32 _eventCounter = 0, _peopleCounter = 0, _sessionStartEpoch; 376 | private static String _sessionID; 377 | private static System.Random _random = new System.Random(Guid.NewGuid().GetHashCode()); 378 | 379 | internal static void InitSession() { 380 | _eventCounter = 0; 381 | _peopleCounter = 0; 382 | _sessionID = Convert.ToString(_random.Next(0, Int32.MaxValue), 16); 383 | _sessionStartEpoch = (Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; 384 | } 385 | internal static Value GetEventMetadata() { 386 | Value eventMetadata = new Value 387 | { 388 | {"$mp_event_id", Convert.ToString(_random.Next(0, Int32.MaxValue), 16)}, 389 | {"$mp_session_id", _sessionID}, 390 | {"$mp_session_seq_id", _eventCounter}, 391 | {"$mp_session_start_sec", _sessionStartEpoch} 392 | }; 393 | _eventCounter++; 394 | return eventMetadata; 395 | } 396 | 397 | internal static Value GetPeopleMetadata() { 398 | Value peopleMetadata = new Value 399 | { 400 | {"$mp_event_id", Convert.ToString(_random.Next(0, Int32.MaxValue), 16)}, 401 | {"$mp_session_id", _sessionID}, 402 | {"$mp_session_seq_id", _peopleCounter}, 403 | {"$mp_session_start_sec", _sessionStartEpoch} 404 | }; 405 | _peopleCounter++; 406 | return peopleMetadata; 407 | } 408 | } 409 | } 410 | } 411 | -------------------------------------------------------------------------------- /Mixpanel/Controller.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9e9e9b07cbbdc4cd0bdb3da8b7a59426 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Mixpanel/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 35d0f5b416bd62b42ae8a6c5fc1a4523 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Mixpanel/Editor/Mixpanel.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Mixpanel.Editor", 3 | "references": [ 4 | "Mixpanel" 5 | ], 6 | "optionalUnityReferences": [], 7 | "includePlatforms": [ 8 | "Editor" 9 | ], 10 | "excludePlatforms": [], 11 | "allowUnsafeCode": false, 12 | "overrideReferences": false, 13 | "precompiledReferences": [], 14 | "autoReferenced": true, 15 | "defineConstraints": [] 16 | } -------------------------------------------------------------------------------- /Mixpanel/Editor/Mixpanel.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4b36be810ca32874697e674b28afe620 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Mixpanel/Editor/MixpanelSettingsEditor.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | 3 | #if UNITY_2018_3_OR_NEWER 4 | namespace mixpanel.editor 5 | { 6 | internal class MixpanelSettingsEditor 7 | { 8 | [SettingsProvider] 9 | internal static SettingsProvider CreateCustomSettingsProvider() 10 | { 11 | // First parameter is the path in the Settings window. 12 | // Second parameter is the scope of this setting: it only appears in the Project Settings window. 13 | var provider = new SettingsProvider("Project/Mixpanel", SettingsScope.Project) 14 | { 15 | // Create the SettingsProvider and initialize its drawing (IMGUI) function in place: 16 | guiHandler = (searchContext) => 17 | { 18 | Editor.CreateEditor(MixpanelSettings.Instance).OnInspectorGUI(); 19 | }, 20 | 21 | // Populate the search keywords to enable smart search filtering and label highlighting: 22 | keywords = SettingsProvider.GetSearchKeywordsFromSerializedObject(new SerializedObject(MixpanelSettings.Instance)) 23 | }; 24 | 25 | return provider; 26 | } 27 | } 28 | } 29 | #endif -------------------------------------------------------------------------------- /Mixpanel/Editor/MixpanelSettingsEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 67e8084bae4d20a4bbefc0c285ae1440 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Mixpanel/Editor/MixpanelSettingsInspector.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace mixpanel.editor 5 | { 6 | [CustomEditor(typeof(MixpanelSettings))] 7 | internal class MixpanelSettingsInspector : Editor 8 | { 9 | public override void OnInspectorGUI() 10 | { 11 | base.OnInspectorGUI(); 12 | EditorGUILayout.Space(); 13 | EditorGUI.DrawRect(EditorGUILayout.BeginVertical(), new Color(0.4f, 0.4f, 0.4f)); 14 | EditorGUILayout.LabelField(new GUIContent("DistinctId", "The current distinct ID that will be sent in API calls."), new GUIContent(MixpanelStorage.DistinctId)); 15 | EditorGUILayout.LabelField(new GUIContent("IsTracking", "The current value of the IsTracking property."), new GUIContent(MixpanelStorage.IsTracking.ToString())); 16 | EditorGUILayout.EndVertical(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Mixpanel/Editor/MixpanelSettingsInspector.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e8d9516909e047d469dede4ed41649e6 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Mixpanel/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace mixpanel 5 | { 6 | internal static class Extensions 7 | { 8 | internal static IEnumerable> Batch(this IEnumerable items, int maxItems) 9 | { 10 | return items 11 | .Select((item, inx) => new { item, inx }) 12 | .GroupBy(x => x.inx / maxItems) 13 | .Select(g => g.Select(x => x.item)); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Mixpanel/Extensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f559f7e3d2538ed4388de3d5fb4842ec 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Mixpanel/IPreferences.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace mixpanel 3 | { 4 | public interface IPreferences 5 | { 6 | void DeleteKey(string key); 7 | int GetInt(string key); 8 | int GetInt(string key, int defaultValue); 9 | string GetString(string key); 10 | string GetString(string key, string defaultValue); 11 | bool HasKey(string key); 12 | void SetInt(string key, int value); 13 | void SetString(string key, string value); 14 | } 15 | } -------------------------------------------------------------------------------- /Mixpanel/IPreferences.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1230616897949d949b682225a1e4d192 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Mixpanel/Log.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace mixpanel 4 | { 5 | public static partial class Mixpanel 6 | { 7 | public static void Log(string s) 8 | { 9 | if (Config.ShowDebug) 10 | { 11 | Debug.Log("[Mixpanel] " + s); 12 | } 13 | } 14 | 15 | public static void LogError(string s) 16 | { 17 | if (Config.ShowDebug) 18 | { 19 | Debug.LogError("[Mixpanel] " + s); 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Mixpanel/Log.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8f4e90df291a94db580d054976c1d9af 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Mixpanel/Mixpanel.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Mixpanel" 3 | } 4 | -------------------------------------------------------------------------------- /Mixpanel/Mixpanel.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 21462402cefc5dd4ba284fb769521eff 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Mixpanel/MixpanelAPI.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace mixpanel 4 | { 5 | /// 6 | /// Core class for interacting with %Mixpanel Analytics. 7 | /// 8 | /// 9 | ///

Open unity project settings and set the properties in the unity inspector (token, debug token, etc.)

10 | ///

Once you have the mixpanel settings setup, you can track events by using Mixpanel.Track(string eventName). 11 | /// You can also update %People Analytics records with Mixpanel.people.

12 | ///
13 | /// 14 | /// //Track an event in Mixpanel Engagement
15 | /// Mixpanel.Track("Hello World");
16 | /// Mixpanel.Identify("CURRENT USER DISTINCT ID");
17 | /// Mixpanel.People.Set("Plan", "Premium");
18 | ///
19 | public static partial class Mixpanel 20 | { 21 | internal const string MixpanelUnityVersion = "3.5.3"; 22 | 23 | /// 24 | /// Creates an Mixpanel instance. Use only if you have enabled "Manual Initialization" from your Project Settings. 25 | /// Do not forget to call Disable() when you want to dispose your object. 26 | /// 27 | public static void Init() 28 | { 29 | Controller.Initialize(); 30 | } 31 | 32 | /// 33 | /// Checks whether Mixpanel is initialized or not. If it is not, every API will be no-op. 34 | /// 35 | public static bool IsInitialized() 36 | { 37 | bool initialized = Controller.IsInitialized(); 38 | if (!initialized) { 39 | Mixpanel.Log("Mixpanel is not initialized"); 40 | } 41 | return initialized; 42 | } 43 | 44 | /// 45 | /// By default, Mixpanel uses PlayerPreferences for data persistence. However you can call this method to 46 | /// set the data persistence of your choice as long as it follows IPeferences 47 | /// 48 | /// the new distinct_id that should represent original 49 | public static void SetPreferencesSource(IPreferences preferences) 50 | { 51 | MixpanelStorage.SetPreferencesSource(preferences); 52 | } 53 | 54 | /// 55 | /// Creates a distinct_id alias. 56 | /// 57 | /// the new distinct_id that should represent original 58 | public static void Alias(string alias) 59 | { 60 | if (!IsInitialized()) return; 61 | if (alias == MixpanelStorage.DistinctId) return; 62 | Value properties = new Value(); 63 | properties["alias"] = alias; 64 | Track("$create_alias", properties); 65 | Flush(); 66 | } 67 | 68 | /// 69 | /// Clears all current event timers. 70 | /// 71 | public static void ClearTimedEvents() 72 | { 73 | if (!IsInitialized()) return; 74 | MixpanelStorage.ResetTimedEvents(); 75 | } 76 | 77 | /// 78 | /// Clears the event timer for a single event. 79 | /// 80 | /// the name of event to clear event timer 81 | public static void ClearTimedEvent(string eventName) 82 | { 83 | if (!IsInitialized()) return; 84 | Value properties = MixpanelStorage.TimedEvents; 85 | properties.Remove(eventName); 86 | MixpanelStorage.TimedEvents = properties; 87 | } 88 | 89 | /// 90 | /// Sets the distinct ID of the current user. 91 | /// 92 | /// a string uniquely identifying this user. Events sent to %Mixpanel 93 | /// using the same distinct_id will be considered associated with the same visitor/customer for 94 | /// retention and funnel reporting, so be sure that the given value is globally unique for each 95 | /// individual user you intend to track. 96 | /// 97 | public static void Identify(string uniqueId) 98 | { 99 | if (!IsInitialized()) return; 100 | if (MixpanelStorage.DistinctId == uniqueId) return; 101 | string oldDistinctId = MixpanelStorage.DistinctId; 102 | MixpanelStorage.DistinctId = uniqueId; 103 | Track("$identify", "$anon_distinct_id", oldDistinctId); 104 | } 105 | 106 | [Obsolete("Please use 'DistinctId' instead!")] 107 | public static string DistinctID { 108 | get => MixpanelStorage.DistinctId; 109 | } 110 | 111 | public static string DistinctId { 112 | get => MixpanelStorage.DistinctId; 113 | } 114 | 115 | /// 116 | /// Opt out tracking. 117 | /// 118 | public static void OptOutTracking() 119 | { 120 | if (!IsInitialized()) return; 121 | People.DeleteUser(); 122 | Flush(); 123 | Reset(); 124 | MixpanelStorage.IsTracking = false; 125 | } 126 | 127 | /// 128 | /// Opt in tracking. 129 | /// 130 | public static void OptInTracking() 131 | { 132 | if (!IsInitialized()) return; 133 | MixpanelStorage.IsTracking = true; 134 | Controller.DoTrack("$opt_in", null); 135 | } 136 | 137 | /// 138 | /// Opt in tracking. 139 | /// 140 | /// the distinct id for events. Behind the scenes, 141 | /// Identify will be called by using this distinct id. 142 | public static void OptInTracking(string distinctId) 143 | { 144 | if (!IsInitialized()) return; 145 | Identify(distinctId); 146 | OptInTracking(); 147 | } 148 | 149 | /// 150 | /// Registers super properties, overwriting ones that have already been set. 151 | /// 152 | /// name of the property to register 153 | /// value of the property to register 154 | public static void Register(string key, Value value) 155 | { 156 | if (!IsInitialized()) return; 157 | Value properties = MixpanelStorage.SuperProperties; 158 | properties[key] = value; 159 | MixpanelStorage.SuperProperties = properties; 160 | } 161 | 162 | /// 163 | /// Registers super properties without overwriting ones that have already been set. 164 | /// 165 | /// name of the property to register 166 | /// value of the property to register 167 | public static void RegisterOnce(string key, Value value) 168 | { 169 | if (!IsInitialized()) return; 170 | Value properties = MixpanelStorage.OnceProperties; 171 | if (properties[key].IsNull) { 172 | properties[key] = value; 173 | MixpanelStorage.OnceProperties = properties; 174 | } 175 | } 176 | 177 | /// 178 | /// Clears all super properties, once properties, timed events from persistent MixpanelStorage. 179 | /// 180 | public static void Reset() 181 | { 182 | if (!IsInitialized()) return; 183 | MixpanelStorage.DeleteAllTrackingData(MixpanelStorage.FlushType.EVENTS); 184 | MixpanelStorage.DeleteAllTrackingData(MixpanelStorage.FlushType.PEOPLE); 185 | MixpanelStorage.ResetSuperProperties(); 186 | MixpanelStorage.ResetOnceProperties(); 187 | MixpanelStorage.ResetTimedEvents(); 188 | Flush(); 189 | MixpanelStorage.DistinctId = ""; 190 | } 191 | 192 | /// 193 | /// Clears all items from the Track and Engage request queues, anything not already sent to the Mixpanel 194 | /// API will no longer be sent 195 | /// 196 | public static void Clear() 197 | { 198 | if (!IsInitialized()) return; 199 | Controller.DoClear(); 200 | } 201 | 202 | /// 203 | /// Clears all super properties 204 | /// 205 | public static void ClearSuperProperties() 206 | { 207 | MixpanelStorage.ResetSuperProperties(); 208 | } 209 | 210 | /// 211 | /// Start timing of an event. Calling Mixpanel.StartTimedEvent(string eventName) will not send an event, 212 | /// but when you eventually call Mixpanel.Track(string eventName), your tracked event will be sent with a "$duration" property, 213 | /// representing the number of seconds between your calls. 214 | /// 215 | /// the name of the event to track with timing 216 | public static void StartTimedEvent(string eventName) 217 | { 218 | if (!IsInitialized()) return; 219 | Value properties = MixpanelStorage.TimedEvents; 220 | properties[eventName] = Util.CurrentTimeInSeconds(); 221 | MixpanelStorage.TimedEvents = properties; 222 | } 223 | 224 | /// 225 | /// Begin timing of an event, but only if the event has not already been registered as a timed event. 226 | /// Useful if you want to know the duration from the point in time the event was first registered. 227 | /// 228 | /// the name of the event to track with timing 229 | public static void StartTimedEventOnce(string eventName) 230 | { 231 | if (!IsInitialized()) return; 232 | if (!MixpanelStorage.TimedEvents.ContainsKey(eventName)) 233 | { 234 | Value properties = MixpanelStorage.TimedEvents; 235 | properties[eventName] = Util.CurrentTimeInSeconds(); 236 | MixpanelStorage.TimedEvents = properties; 237 | } 238 | } 239 | 240 | /// 241 | /// Tracks an event. 242 | /// 243 | /// the name of the event to send 244 | public static void Track(string eventName) 245 | { 246 | if (!IsInitialized()) return; 247 | Controller.DoTrack(eventName, null); 248 | } 249 | 250 | /// 251 | /// Tracks an event with properties of key=value. 252 | /// 253 | /// the name of the event to send 254 | /// A Key value for the data 255 | /// The value to use for the key 256 | public static void Track(string eventName, string key, Value value) 257 | { 258 | if (!IsInitialized()) return; 259 | Value properties = new Value(); 260 | properties[key] = value; 261 | Controller.DoTrack(eventName, properties); 262 | } 263 | 264 | /// 265 | /// Tracks an event with properties. 266 | /// 267 | /// the name of the event to send 268 | /// A Value containing the key value pairs of the properties 269 | /// to include in this event. Pass null if no extra properties exist. 270 | /// 271 | public static void Track(string eventName, Value properties) { 272 | if (!IsInitialized()) return; 273 | Controller.DoTrack(eventName, properties); 274 | } 275 | 276 | /// 277 | /// Removes a single superProperty. 278 | /// 279 | /// name of the property to unregister 280 | public static void Unregister(string key) 281 | { 282 | if (!IsInitialized()) return; 283 | Value properties = MixpanelStorage.SuperProperties; 284 | properties.Remove(key); 285 | MixpanelStorage.SuperProperties = properties; 286 | } 287 | 288 | /// 289 | /// Flushes the queued data to Mixpanel 290 | /// 291 | /// callback to be called when the flush is complete. Returns true if overall success 292 | public static void Flush(Action onFlushComplete = null) 293 | { 294 | if (!IsInitialized()) return; 295 | Controller.GetInstance().DoFlush(onFlushComplete); 296 | } 297 | 298 | /// 299 | /// Sets the project token to be used. This setting will override what it is set in the Unity Project Settings. 300 | /// 301 | public static void SetToken(string token) 302 | { 303 | if (!IsInitialized()) return; 304 | MixpanelSettings.Instance.DebugToken = token; 305 | MixpanelSettings.Instance.RuntimeToken = token; 306 | } 307 | 308 | /// 309 | /// Disables Mixpanel Component. Useful if you have "Manual Initialization" enabled under your Project Settings. 310 | /// 311 | public static void Disable() 312 | { 313 | if (!IsInitialized()) return; 314 | Controller.Disable(); 315 | } 316 | 317 | /// 318 | /// Core interface for using %Mixpanel %People Analytics features. You can get an instance by calling Mixpanel.people 319 | /// 320 | public static class People 321 | { 322 | /// 323 | /// Append values to list properties. 324 | /// 325 | /// mapping of list property names to values to append 326 | public static void Append(Value properties) 327 | { 328 | if (!IsInitialized()) return; 329 | Controller.DoEngage(new Value {{"$append", properties}}); 330 | } 331 | 332 | /// 333 | /// Appends a value to a list-valued property. 334 | /// 335 | /// the %People Analytics property that should have it's value appended to 336 | /// the new value that will appear at the end of the property's list 337 | public static void Append(string property, Value values) 338 | { 339 | Append(new Value {{property, values}}); 340 | } 341 | 342 | /// 343 | /// Permanently clear the whole transaction history for the identified people profile. 344 | /// 345 | public static void ClearCharges() 346 | { 347 | Unset("$transactions"); 348 | } 349 | 350 | /// 351 | /// Permanently delete the identified people profile 352 | /// 353 | public static void DeleteUser() 354 | { 355 | if (!IsInitialized()) return; 356 | Controller.DoEngage(new Value {{"$delete", ""}}); 357 | } 358 | 359 | /// 360 | /// Change the existing values of multiple %People Analytics properties at once. 361 | /// 362 | /// A map of String properties names to Long amounts. Each property associated with a name in the map 363 | public static void Increment(Value properties) 364 | { 365 | if (!IsInitialized()) return; 366 | Controller.DoEngage(new Value {{"$add", properties}}); 367 | } 368 | 369 | /// 370 | /// Convenience method for incrementing a single numeric property by the specified amount. 371 | /// 372 | /// property name 373 | /// amount to increment by 374 | public static void Increment(string property, Value by) 375 | { 376 | Increment(new Value {{property, by}}); 377 | } 378 | 379 | /// 380 | /// Set a collection of properties on the identified user all at once. 381 | /// 382 | /// a JSONObject containing the collection of properties you wish to apply 383 | /// to the identified user. Each key in the JSONObject will be associated with a property name, and the value 384 | /// of that key will be assigned to the property. 385 | /// 386 | public static void Set(Value properties) 387 | { 388 | if (!IsInitialized()) return; 389 | properties.Merge(Controller.GetEngageDefaultProperties()); 390 | Controller.DoEngage(new Value {{"$set", properties}}); 391 | } 392 | 393 | /// 394 | /// Sets a single property with the given name and value for this user. 395 | /// 396 | /// property name 397 | /// property value 398 | public static void Set(string property, Value to) 399 | { 400 | Set(new Value {{property, to}}); 401 | } 402 | 403 | /// 404 | /// Like Mixpanel.Set(Value properties), but will not set properties that already exist on a record. 405 | /// 406 | /// a JSONObject containing the collection of properties you wish to apply to the identified user. Each key in the JSONObject will be associated with a property name, and the value of that key will be assigned to the property. 407 | public static void SetOnce(Value properties) 408 | { 409 | if (!IsInitialized()) return; 410 | Controller.DoEngage(new Value {{"$set_once", properties}}); 411 | } 412 | 413 | /// 414 | /// Like Mixpanel.Set(string property, Value to), but will not set properties that already exist on a record. 415 | /// 416 | /// property name 417 | /// property value 418 | public static void SetOnce(string property, Value to) 419 | { 420 | SetOnce(new Value {{property, to}}); 421 | } 422 | 423 | /// 424 | /// Track a revenue transaction for the identified people profile. 425 | /// 426 | /// amount of revenue received 427 | /// a JSONObject containing the collection of properties you wish to apply 428 | public static void TrackCharge(double amount, Value properties) 429 | { 430 | properties["$amount"] = amount; 431 | TrackCharge(properties); 432 | } 433 | 434 | /// 435 | /// Track a revenue transaction for the identified people profile. 436 | /// 437 | /// amount of revenue received 438 | public static void TrackCharge(double amount) 439 | { 440 | TrackCharge(new Value {{"$amount", amount}}); 441 | } 442 | 443 | /// 444 | /// Track a revenue transaction for the identified people profile. 445 | /// 446 | /// a JSONObject containing the collection of properties you wish to apply 447 | public static void TrackCharge(Value properties) 448 | { 449 | if (!IsInitialized()) return; 450 | properties["$time"] = Util.CurrentDateTime(); 451 | Controller.DoEngage(new Value {{"$append", new Value {{"$transactions", properties}}}}); 452 | } 453 | 454 | /// 455 | /// Adds values to a list-valued property only if they are not already present in the list. 456 | /// If the property does not currently exist, it will be created with the given list as it's value. 457 | /// If the property exists and is not list-valued, the union will be ignored. 458 | /// 459 | /// mapping of list property names to lists to union 460 | public static void Union(Value properties) 461 | { 462 | if (!IsInitialized()) return; 463 | Controller.DoEngage(new Value {{"$union", properties}}); 464 | } 465 | 466 | /// 467 | /// Adds values to a list-valued property only if they are not already present in the list. 468 | /// If the property does not currently exist, it will be created with the given list as it's value. 469 | /// If the property exists and is not list-valued, the union will be ignored. /// 470 | /// name of the list-valued property to set or modify 471 | /// an array of values to add to the property value if not already present 472 | public static void Union(string property, Value values) 473 | { 474 | if (!values.IsArray) 475 | throw new ArgumentException("Union with values property must be an array", nameof(values)); 476 | Union(new Value {{property, values}}); 477 | } 478 | 479 | /// 480 | /// Takes a string property name, and permanently removes the property and their values from a profile. 481 | /// 482 | /// property 483 | public static void Unset(string property) 484 | { 485 | if (!IsInitialized()) return; 486 | Controller.DoEngage(new Value {{"$unset", new string[]{property}}}); 487 | } 488 | 489 | /// 490 | /// Sets the email for this user. 491 | /// 492 | public static string Email 493 | { 494 | set => Set(new Value {{"$email", value}}); 495 | } 496 | 497 | /// 498 | /// Sets the first name for this user. 499 | /// 500 | public static string FirstName 501 | { 502 | set => Set(new Value {{"$first_name", value}}); 503 | } 504 | 505 | /// 506 | /// Sets the last name for this user. 507 | /// 508 | public static string LastName 509 | { 510 | set => Set(new Value {{"$last_name", value}}); 511 | } 512 | 513 | /// 514 | /// Sets the name for this user. 515 | /// 516 | public static string Name 517 | { 518 | set => Set(new Value {{"$name", value}}); 519 | } 520 | 521 | } 522 | } 523 | } 524 | -------------------------------------------------------------------------------- /Mixpanel/MixpanelAPI.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ea3374841c8fd5e4785fa356650d2485 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Mixpanel/MixpanelSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using UnityEngine; 5 | #if UNITY_EDITOR 6 | using UnityEditor; 7 | #endif 8 | 9 | namespace mixpanel 10 | { 11 | public class MixpanelSettings : ScriptableObject 12 | { 13 | private const string TrackUrlTemplate = "{0}track/?ip={1}"; 14 | private const string EngageUrlTemplate = "{0}engage/?ip={1}"; 15 | 16 | //TODO: Convert to log level 17 | [Tooltip("If true will print helpful debugging messages")] 18 | public bool ShowDebug; 19 | [Tooltip("If true, you need to manually initialize the library")] 20 | public bool ManualInitialization; 21 | [Tooltip("The api host of where to send the requests to. Useful when you need to proxy all the request to somewhere else.'")] 22 | public string APIHostAddress = "https://api.mixpanel.com/"; 23 | [Tooltip("The token of the Mixpanel project.")] 24 | public string RuntimeToken = ""; 25 | [Tooltip("Used when the DEBUG compile flag is set or when in the editor. Useful if you want to use different tokens for test builds.")] 26 | public string DebugToken = ""; 27 | [Tooltip("Seconds (in realtime) between sending data to the API Host.")] 28 | public float FlushInterval = 60f; 29 | [Tooltip("If true, the library will use the IP address of the client for geolocation. If false, the library will use the IP address of the Mixpanel server for geolocation.")] 30 | public bool UseIpAddressForGeolocation = true; 31 | 32 | internal string Token { 33 | get { 34 | #if UNITY_EDITOR || DEBUG 35 | return DebugToken; 36 | #else 37 | return RuntimeToken; 38 | #endif 39 | } 40 | } 41 | 42 | public void ApplyToConfig() 43 | { 44 | string host = APIHostAddress.EndsWith("/") ? APIHostAddress : $"{APIHostAddress}/"; 45 | string useIpAddressForGeolocation = UseIpAddressForGeolocation ? "1" : "0"; 46 | Config.TrackUrl = string.Format(TrackUrlTemplate, host, useIpAddressForGeolocation); 47 | Config.EngageUrl = string.Format(EngageUrlTemplate, host, useIpAddressForGeolocation); 48 | Config.ShowDebug = ShowDebug; 49 | Config.ManualInitialization = ManualInitialization; 50 | Config.FlushInterval = FlushInterval; 51 | } 52 | 53 | #region static 54 | private static MixpanelSettings _instance; 55 | 56 | public static void LoadSettings() 57 | { 58 | if (!_instance) 59 | { 60 | _instance = FindOrCreateInstance(); 61 | _instance.ApplyToConfig(); 62 | } 63 | } 64 | 65 | public static MixpanelSettings Instance { 66 | get { 67 | LoadSettings(); 68 | return _instance; 69 | } 70 | } 71 | 72 | private static MixpanelSettings FindOrCreateInstance() 73 | { 74 | MixpanelSettings instance = null; 75 | instance = instance ? null : Resources.Load("Mixpanel"); 76 | instance = instance ? instance : Resources.LoadAll(string.Empty).FirstOrDefault(); 77 | instance = instance ? instance : CreateAndSave(); 78 | if (instance == null) throw new Exception("Could not find or create settings for Mixpanel"); 79 | return instance; 80 | } 81 | 82 | private static T CreateAndSave() where T : ScriptableObject 83 | { 84 | T instance = CreateInstance(); 85 | #if UNITY_EDITOR 86 | //Saving during Awake() will crash Unity, delay saving until next editor frame 87 | if (EditorApplication.isPlayingOrWillChangePlaymode) 88 | { 89 | EditorApplication.delayCall += () => SaveAsset(instance); 90 | } 91 | else 92 | { 93 | SaveAsset(instance); 94 | } 95 | #endif 96 | return instance; 97 | } 98 | 99 | #if UNITY_EDITOR 100 | private static void SaveAsset(T obj) where T : ScriptableObject 101 | { 102 | 103 | string dirName = "Assets/Resources"; 104 | if (!Directory.Exists(dirName)) 105 | { 106 | Directory.CreateDirectory(dirName); 107 | } 108 | AssetDatabase.CreateAsset(obj, "Assets/Resources/Mixpanel.asset"); 109 | AssetDatabase.SaveAssets(); 110 | } 111 | #endif 112 | #endregion 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Mixpanel/MixpanelSettings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5e37f1d599a72b64eba3799d826c755b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Mixpanel/PlayerPreferences.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace mixpanel 4 | { 5 | public class PlayerPreferences : IPreferences 6 | { 7 | public void DeleteKey(string key) 8 | { 9 | PlayerPrefs.DeleteKey(key); 10 | } 11 | 12 | public float GetFloat(string key) 13 | { 14 | return PlayerPrefs.GetFloat(key); 15 | } 16 | 17 | public int GetInt(string key) 18 | { 19 | return PlayerPrefs.GetInt(key); 20 | } 21 | 22 | public int GetInt(string key, int defaultValue) 23 | { 24 | return PlayerPrefs.GetInt(key, defaultValue); 25 | } 26 | 27 | public string GetString(string key) 28 | { 29 | return PlayerPrefs.GetString(key); 30 | } 31 | 32 | public string GetString(string key, string defaultValue) 33 | { 34 | return PlayerPrefs.GetString(key, defaultValue); 35 | } 36 | 37 | public bool HasKey(string key) 38 | { 39 | return PlayerPrefs.HasKey(key); 40 | } 41 | 42 | public void SetInt(string key, int value) 43 | { 44 | PlayerPrefs.SetInt(key, value); 45 | } 46 | 47 | public void SetString(string key, string value) 48 | { 49 | PlayerPrefs.SetString(key, value); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /Mixpanel/PlayerPreferences.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: edcd1ac2d0e7eba498446cd39dc68546 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Mixpanel/Storage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Text; 7 | using UnityEngine; 8 | using System.Net; 9 | using System.Net.Http; 10 | using System.Web; 11 | using UnityEngine.Networking; 12 | using Unity.Jobs; 13 | using Unity.Collections; 14 | 15 | namespace mixpanel 16 | { 17 | public static class MixpanelStorage 18 | { 19 | #region Preferences 20 | private static IPreferences PreferencesSource = new PlayerPreferences(); 21 | 22 | public static void SetPreferencesSource(IPreferences preferences) 23 | { 24 | PreferencesSource = preferences; 25 | } 26 | 27 | #endregion 28 | 29 | #region HasMigratedFrom1To2 30 | 31 | private const string HasMigratedFrom1To2Name = "Mixpanel.HasMigratedFrom1To2"; 32 | 33 | internal static bool HasMigratedFrom1To2 34 | { 35 | get => Convert.ToBoolean(PreferencesSource.GetInt(HasMigratedFrom1To2Name, 0)); 36 | set => PreferencesSource.SetInt(HasMigratedFrom1To2Name, Convert.ToInt32(value)); 37 | } 38 | 39 | #endregion 40 | 41 | #region DistinctId 42 | 43 | private const string DistinctIdName = "Mixpanel.DistinctId"; 44 | 45 | private static string _distinctId; 46 | 47 | public static string DistinctId 48 | { 49 | get 50 | { 51 | if (!string.IsNullOrEmpty(_distinctId)) return _distinctId; 52 | if (PreferencesSource.HasKey(DistinctIdName)) 53 | { 54 | _distinctId = PreferencesSource.GetString(DistinctIdName); 55 | } 56 | // Generate a Unique ID for this client if still null or empty 57 | // https://devblogs.microsoft.com/oldnewthing/?p=21823 58 | if (string.IsNullOrEmpty(_distinctId)) DistinctId = Guid.NewGuid().ToString(); 59 | return _distinctId; 60 | } 61 | set 62 | { 63 | _distinctId = value; 64 | PreferencesSource.SetString(DistinctIdName, _distinctId); 65 | } 66 | } 67 | 68 | #endregion 69 | 70 | #region Track 71 | 72 | private const string EventAutoIncrementingIdName = "EventAutoIncrementingID"; 73 | private const string PeopleAutoIncrementingIdName = "PeopleAutoIncrementingID"; 74 | 75 | // For performance, we can store the lowest unsent event ID to prevent searching from 0. 76 | // This search process can be slow if the auto-increment ID gets large enough. 77 | private const string EventStartIndexName = "EventStartIndex"; 78 | private const string PeopleStartIndexName = "PeopleStartIndex"; 79 | 80 | internal enum FlushType 81 | { 82 | EVENTS, 83 | PEOPLE, 84 | } 85 | 86 | internal static void EnqueueTrackingData(Value data, FlushType flushType) 87 | { 88 | int eventId = EventAutoIncrementingID(); 89 | int peopleId = PeopleAutoIncrementingID(); 90 | String trackingKey = (flushType == FlushType.EVENTS)? "Event" + eventId.ToString() : "People" + peopleId.ToString(); 91 | data["id"] = trackingKey; 92 | PreferencesSource.SetString(trackingKey, JsonUtility.ToJson(data)); 93 | IncreaseTrackingDataID(flushType); 94 | } 95 | 96 | internal static int EventAutoIncrementingID() 97 | { 98 | return PreferencesSource.GetInt(EventAutoIncrementingIdName, 0); 99 | } 100 | 101 | internal static int PeopleAutoIncrementingID() 102 | { 103 | return PreferencesSource.GetInt(PeopleAutoIncrementingIdName, 0); 104 | } 105 | 106 | internal static int EventStartIndex() 107 | { 108 | return PreferencesSource.GetInt(EventStartIndexName, 0); 109 | } 110 | 111 | internal static int PeopleStartIndex() 112 | { 113 | return PreferencesSource.GetInt(PeopleStartIndexName, 0); 114 | } 115 | 116 | private static void IncreaseTrackingDataID(FlushType flushType) 117 | { 118 | int id = (flushType == FlushType.EVENTS)? EventAutoIncrementingID() : PeopleAutoIncrementingID(); 119 | id += 1; 120 | String trackingIdKey = (flushType == FlushType.EVENTS)? EventAutoIncrementingIdName : PeopleAutoIncrementingIdName; 121 | PreferencesSource.SetInt(trackingIdKey, id); 122 | } 123 | 124 | internal static Value DequeueBatchTrackingData(FlushType flushType, int batchSize) 125 | { 126 | Value batch = Value.Array; 127 | string startIndexKey = (flushType == FlushType.EVENTS) ? EventStartIndexName : PeopleStartIndexName; 128 | int oldStartIndex = (flushType == FlushType.EVENTS) ? EventStartIndex() : PeopleStartIndex(); 129 | int newStartIndex = oldStartIndex; 130 | int dataIndex = oldStartIndex; 131 | int maxIndex = (flushType == FlushType.EVENTS) ? EventAutoIncrementingID() - 1 : PeopleAutoIncrementingID() - 1; 132 | while (batch.Count < batchSize && dataIndex <= maxIndex) { 133 | String trackingKey = (flushType == FlushType.EVENTS) ? "Event" + dataIndex.ToString() : "People" + dataIndex.ToString(); 134 | if (PreferencesSource.HasKey(trackingKey)) { 135 | try { 136 | batch.Add(JsonUtility.FromJson(PreferencesSource.GetString(trackingKey))); 137 | } 138 | catch (Exception e) { 139 | Mixpanel.LogError($"There was an error processing '{trackingKey}' from the internal object pool: " + e); 140 | PreferencesSource.DeleteKey(trackingKey); 141 | 142 | if (batch.Count == 0) { 143 | // Only update if we didn't find a key prior to deleting this key, since the prior key would be a lower valid index. 144 | newStartIndex = Math.Min(dataIndex + 1, maxIndex); 145 | } 146 | } 147 | } 148 | else if (batch.Count == 0) { 149 | // Keep updating the start index as long as we haven't found anything for our batch yet -- we're looking for the minimum index. 150 | newStartIndex = Math.Min(dataIndex + 1, maxIndex); 151 | } 152 | dataIndex++; 153 | } 154 | 155 | if (newStartIndex != oldStartIndex) { 156 | PreferencesSource.SetInt(startIndexKey, newStartIndex); 157 | } 158 | 159 | return batch; 160 | } 161 | 162 | internal static void DeleteBatchTrackingData(FlushType flushType, int batchSize) 163 | { 164 | int deletedCount = 0; 165 | string startIndexKey = (flushType == FlushType.EVENTS) ? EventStartIndexName : PeopleStartIndexName; 166 | int oldStartIndex = (flushType == FlushType.EVENTS) ? EventStartIndex() : PeopleStartIndex(); 167 | int newStartIndex = oldStartIndex; 168 | int dataIndex = oldStartIndex; 169 | int maxIndex = (flushType == FlushType.EVENTS) ? EventAutoIncrementingID() - 1 : PeopleAutoIncrementingID() - 1; 170 | while (deletedCount < batchSize && dataIndex <= maxIndex) { 171 | String trackingKey = (flushType == FlushType.EVENTS) ? "Event" + dataIndex.ToString() : "People" + dataIndex.ToString(); 172 | if (PreferencesSource.HasKey(trackingKey)) { 173 | PreferencesSource.DeleteKey(trackingKey); 174 | deletedCount++; 175 | } 176 | newStartIndex = Math.Min(dataIndex + 1, maxIndex); 177 | dataIndex++; 178 | } 179 | 180 | bool deletedAllTrackingData = dataIndex > maxIndex; // if true, we iterated through all events. 181 | if (deletedAllTrackingData) { 182 | // For performance reasons, reset the tracking data ID to 0 if all data stored in PlayerPrefs has been deleted. 183 | // Otherwise, there can be a large number of string concatenation and PreferencesSource.Haskey calls (in extreme cases, 100K+). 184 | // See https://github.com/mixpanel/mixpanel-unity/pull/152 for context 185 | string idKey = (flushType == FlushType.EVENTS) ? EventAutoIncrementingIdName : PeopleAutoIncrementingIdName; 186 | PreferencesSource.SetInt(idKey, 0); 187 | PreferencesSource.SetInt(startIndexKey, 0); 188 | } 189 | else if (newStartIndex != oldStartIndex) { 190 | // There are unsent batches, store the index of where to resume searching for next time. 191 | PreferencesSource.SetInt(startIndexKey, newStartIndex); 192 | } 193 | } 194 | 195 | internal static void DeleteBatchTrackingData(Value batch) { 196 | foreach(Value data in batch) { 197 | String id = data["id"]; 198 | if (id != null && PreferencesSource.HasKey(id)) { 199 | PreferencesSource.DeleteKey(id); 200 | } 201 | } 202 | } 203 | 204 | internal static void DeleteAllTrackingData(FlushType flushType) 205 | { 206 | DeleteBatchTrackingData(flushType, int.MaxValue); 207 | } 208 | 209 | #endregion 210 | 211 | #region IsTracking 212 | 213 | private const string IsTrackingName = "Mixpanel.IsTracking"; 214 | 215 | private static bool _isTracking; 216 | 217 | public static bool IsTracking 218 | { 219 | get 220 | { 221 | if (!PreferencesSource.HasKey(IsTrackingName)) IsTracking = true; 222 | else _isTracking = PreferencesSource.GetInt(IsTrackingName) == 1; 223 | return _isTracking; 224 | } 225 | set 226 | { 227 | _isTracking = value; 228 | PreferencesSource.SetInt(IsTrackingName, _isTracking ? 1 : 0); 229 | } 230 | } 231 | 232 | #endregion 233 | 234 | #region OnceProperties 235 | 236 | private const string OncePropertiesName = "Mixpanel.OnceProperties"; 237 | 238 | private static Value _onceProperties; 239 | 240 | internal static Value OnceProperties 241 | { 242 | get 243 | { 244 | if (_onceProperties != null) return _onceProperties; 245 | if (!PreferencesSource.HasKey(OncePropertiesName)) OnceProperties = new Value(); 246 | else 247 | { 248 | _onceProperties = new Value(); 249 | JsonUtility.FromJsonOverwrite(PreferencesSource.GetString(OncePropertiesName), _onceProperties); 250 | } 251 | return _onceProperties; 252 | } 253 | set 254 | { 255 | _onceProperties = value; 256 | PreferencesSource.SetString(OncePropertiesName, JsonUtility.ToJson(_onceProperties)); 257 | } 258 | } 259 | 260 | internal static void ResetOnceProperties() 261 | { 262 | Value properties = OnceProperties; 263 | properties.OnRecycle(); 264 | OnceProperties = properties; 265 | } 266 | 267 | #endregion 268 | 269 | #region SuperProperties 270 | 271 | private const string SuperPropertiesName = "Mixpanel.SuperProperties"; 272 | 273 | private static Value _superProperties; 274 | 275 | internal static Value SuperProperties 276 | { 277 | get 278 | { 279 | if (_superProperties != null) return _superProperties; 280 | if (!PreferencesSource.HasKey(SuperPropertiesName)) SuperProperties = new Value(); 281 | else 282 | { 283 | _superProperties = new Value(); 284 | JsonUtility.FromJsonOverwrite(PreferencesSource.GetString(SuperPropertiesName), _superProperties); 285 | } 286 | return _superProperties; 287 | } 288 | set 289 | { 290 | _superProperties = value; 291 | PreferencesSource.SetString(SuperPropertiesName, JsonUtility.ToJson(_superProperties)); 292 | } 293 | } 294 | 295 | internal static void ResetSuperProperties() 296 | { 297 | Value properties = SuperProperties; 298 | properties.OnRecycle(); 299 | SuperProperties = properties; 300 | } 301 | 302 | #endregion 303 | 304 | #region TimedEvents 305 | 306 | private const string TimedEventsName = "Mixpanel.TimedEvents"; 307 | 308 | private static Value _timedEvents; 309 | 310 | internal static Value TimedEvents 311 | { 312 | get 313 | { 314 | if (_timedEvents != null) return _timedEvents; 315 | if (!PreferencesSource.HasKey(TimedEventsName)) TimedEvents = new Value(); 316 | else 317 | { 318 | _timedEvents = new Value(); 319 | JsonUtility.FromJsonOverwrite(PreferencesSource.GetString(TimedEventsName), _timedEvents); 320 | } 321 | return _timedEvents; 322 | } 323 | set 324 | { 325 | _timedEvents = value; 326 | PreferencesSource.SetString(TimedEventsName, JsonUtility.ToJson(_timedEvents)); 327 | } 328 | } 329 | 330 | internal static void ResetTimedEvents() 331 | { 332 | Value properties = TimedEvents; 333 | properties.OnRecycle(); 334 | TimedEvents = properties; 335 | } 336 | 337 | #endregion 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /Mixpanel/Storage.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8f4bc700729934c7180b4218f6f64ec5 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Mixpanel/Value.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Globalization; 5 | using System.IO; 6 | using System.Text; 7 | using UnityEngine; 8 | using UnityEngine.Assertions; 9 | 10 | namespace mixpanel 11 | { 12 | [Serializable] 13 | public class Value : IEnumerable, ISerializationCallbackReceiver 14 | { 15 | 16 | 17 | private enum ValueTypes 18 | { 19 | UNDEFINED, 20 | NULL, 21 | STRING, 22 | BOOLEAN, 23 | NUMBER, 24 | ARRAY, 25 | OBJECT 26 | } 27 | 28 | private enum DataTypes 29 | { 30 | UNDEFINED, 31 | PRIMITIVE, 32 | CONTAINER, 33 | URI, 34 | GUID, 35 | DATE_TIME, 36 | DATE_TIME_OFFSET, 37 | TIME_SPAN, 38 | COLOR, 39 | VECTOR, 40 | QUATERNION, 41 | BOUNDS, 42 | RECT 43 | } 44 | 45 | [SerializeField] private ValueTypes _valueType = ValueTypes.OBJECT; 46 | [SerializeField] private DataTypes _dataType = DataTypes.UNDEFINED; 47 | [SerializeField] private string _string; 48 | [SerializeField] private bool _bool; 49 | [SerializeField] private double _number; 50 | 51 | [NonSerialized] 52 | private List _array = new List(50); 53 | [SerializeField] private string[] _arrayData; 54 | 55 | [NonSerialized] 56 | private Dictionary _container = new Dictionary(5); 57 | [SerializeField] private string[] _containerKeys; 58 | [SerializeField] private string[] _containerValues; 59 | 60 | public bool IsNull => _valueType == ValueTypes.NULL; 61 | public bool IsArray => _valueType == ValueTypes.ARRAY; 62 | public bool IsObject => _valueType == ValueTypes.OBJECT; 63 | 64 | public void OnRecycle() 65 | { 66 | _string = ""; 67 | _bool = false; 68 | _number = 0; 69 | _array.Clear(); 70 | _arrayData = null; 71 | _container.Clear(); 72 | _containerKeys = null; 73 | _containerValues = null; 74 | } 75 | 76 | public Value this[int index] 77 | { 78 | get => _array[index]; 79 | set 80 | { 81 | Assert.IsTrue(_valueType == ValueTypes.ARRAY || _valueType == ValueTypes.UNDEFINED, 82 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.ARRAY or ValueTypes.UNDEFINED." 83 | ); 84 | _valueType = ValueTypes.ARRAY; 85 | _dataType = DataTypes.CONTAINER; 86 | _array[index] = value; 87 | } 88 | } 89 | 90 | public Value this[string key] 91 | { 92 | get 93 | { 94 | if (!_container.ContainsKey(key)) _container[key] = new Value(); 95 | return _container[key]; 96 | } 97 | set 98 | { 99 | Assert.IsTrue(_valueType == ValueTypes.OBJECT || _valueType == ValueTypes.UNDEFINED, 100 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.OBJECT or ValueTypes.UNDEFINED." 101 | ); 102 | _valueType = ValueTypes.OBJECT; 103 | _dataType = DataTypes.CONTAINER; 104 | _container[key] = value; 105 | } 106 | } 107 | 108 | public override string ToString() 109 | { 110 | switch (_valueType) 111 | { 112 | case ValueTypes.UNDEFINED: 113 | case ValueTypes.NULL: 114 | return "null"; 115 | case ValueTypes.STRING: 116 | return _string; 117 | case ValueTypes.BOOLEAN: 118 | return _bool.ToString(); 119 | case ValueTypes.NUMBER: 120 | return _number.ToString(CultureInfo.InvariantCulture); 121 | case ValueTypes.ARRAY: 122 | StringWriter arrayWriter = new StringWriter(); 123 | Write(arrayWriter); 124 | return arrayWriter.ToString(); 125 | case ValueTypes.OBJECT: 126 | StringWriter containerWriter = new StringWriter(); 127 | Write(containerWriter); 128 | return containerWriter.ToString(); 129 | default: 130 | throw new ArgumentOutOfRangeException(); 131 | } 132 | } 133 | 134 | public IEnumerator GetEnumerator() 135 | { 136 | Assert.IsTrue(_valueType == ValueTypes.ARRAY || _valueType == ValueTypes.OBJECT, 137 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.ARRAY or ValueTypes.OBJECT." 138 | ); 139 | switch (_valueType) 140 | { 141 | case ValueTypes.ARRAY: 142 | return _array.GetEnumerator(); 143 | case ValueTypes.OBJECT: 144 | return _container.GetEnumerator(); 145 | } 146 | throw new ArgumentOutOfRangeException(); 147 | } 148 | 149 | public int Count 150 | { 151 | get 152 | { 153 | Assert.IsTrue(_valueType == ValueTypes.ARRAY || _valueType == ValueTypes.OBJECT, 154 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.ARRAY or ValueTypes.OBJECT." 155 | ); 156 | switch (_valueType) 157 | { 158 | case ValueTypes.ARRAY: 159 | return _array.Count; 160 | case ValueTypes.OBJECT: 161 | return _container.Count; 162 | } 163 | throw new ArgumentOutOfRangeException(); 164 | } 165 | } 166 | 167 | public bool Contains(int index) 168 | { 169 | Assert.IsTrue(_valueType == ValueTypes.ARRAY, 170 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.ARRAY." 171 | ); 172 | return _array.Contains(index); 173 | } 174 | 175 | public bool ContainsKey(string key) 176 | { 177 | Assert.IsTrue(_valueType == ValueTypes.OBJECT, 178 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.OBJECT." 179 | ); 180 | return _container.ContainsKey(key); 181 | } 182 | 183 | public void Add(Value value) 184 | { 185 | Assert.IsTrue(_valueType == ValueTypes.ARRAY || _valueType == ValueTypes.UNDEFINED, 186 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.ARRAY or ValueTypes.UNDEFINED." 187 | ); 188 | _valueType = ValueTypes.ARRAY; 189 | _dataType = DataTypes.CONTAINER; 190 | _array.Add(value); 191 | } 192 | 193 | public void Add(string key, Value value) 194 | { 195 | Assert.IsTrue(_valueType == ValueTypes.OBJECT || _valueType == ValueTypes.UNDEFINED, 196 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.OBJECT or ValueTypes.UNDEFINED." 197 | ); 198 | _valueType = ValueTypes.OBJECT; 199 | _dataType = DataTypes.CONTAINER; 200 | _container.Add(key, value); 201 | } 202 | 203 | public void Remove(int index) 204 | { 205 | Assert.IsTrue(_valueType == ValueTypes.ARRAY, 206 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.ARRAY." 207 | ); 208 | _array.Remove(index); 209 | } 210 | 211 | public void Remove(string key) 212 | { 213 | Assert.IsTrue(_valueType == ValueTypes.OBJECT, 214 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.OBJECT." 215 | ); 216 | _container.Remove(key); 217 | } 218 | 219 | public IEnumerable Values 220 | { 221 | get 222 | { 223 | Assert.IsTrue(_valueType == ValueTypes.ARRAY || _valueType == ValueTypes.OBJECT, 224 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.ARRAY or ValueTypes.OBJECT." 225 | ); 226 | switch (_valueType) 227 | { 228 | case ValueTypes.ARRAY: 229 | return _array; 230 | case ValueTypes.OBJECT: 231 | return _container.Values; 232 | } 233 | throw new ArgumentOutOfRangeException(); 234 | } 235 | } 236 | 237 | public bool TryGetValue(string key, out Value value) 238 | { 239 | Assert.IsTrue(_valueType == ValueTypes.OBJECT, 240 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.OBJECT." 241 | ); 242 | return _container.TryGetValue(key, out value); 243 | } 244 | 245 | public void Merge(Value other) 246 | { 247 | Assert.IsTrue(_valueType == ValueTypes.ARRAY || _valueType == ValueTypes.OBJECT, 248 | $"Merge operation failed: _valueType is {_valueType}, but expected ValueTypes.ARRAY or ValueTypes.OBJECT." 249 | ); 250 | switch (other._valueType) 251 | { 252 | case ValueTypes.ARRAY: 253 | _array.AddRange(other._array); 254 | return; 255 | case ValueTypes.OBJECT: 256 | foreach (string key in other._container.Keys) 257 | { 258 | _container[key] = other._container[key]; 259 | } 260 | return; 261 | default: 262 | throw new ArgumentException("Unable to merge! Value to merge with is not a 'Array' or 'Object' type."); 263 | } 264 | } 265 | 266 | #region Types 267 | 268 | private string String 269 | { 270 | get 271 | { 272 | Assert.IsTrue(_valueType == ValueTypes.STRING && _dataType == DataTypes.PRIMITIVE, 273 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.STRING or ValueTypes.PRIMITIVE." 274 | ); 275 | return _string; 276 | } 277 | set 278 | { 279 | _valueType = ValueTypes.STRING; 280 | _dataType = DataTypes.PRIMITIVE; 281 | _string = value; 282 | } 283 | } 284 | 285 | private bool Bool 286 | { 287 | get 288 | { 289 | Assert.IsTrue(_valueType == ValueTypes.BOOLEAN && _dataType == DataTypes.PRIMITIVE, 290 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.BOOLEAN or ValueTypes.PRIMITIVE." 291 | ); 292 | return _bool; 293 | } 294 | set 295 | { 296 | _valueType = ValueTypes.BOOLEAN; 297 | _dataType = DataTypes.PRIMITIVE; 298 | _bool = value; 299 | } 300 | } 301 | 302 | private double Number 303 | { 304 | get 305 | { 306 | Assert.IsTrue(_valueType == ValueTypes.NUMBER && _dataType == DataTypes.PRIMITIVE, 307 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.NUMBER or ValueTypes.PRIMITIVE." 308 | ); 309 | return _number; 310 | } 311 | set 312 | { 313 | _valueType = ValueTypes.NUMBER; 314 | _dataType = DataTypes.PRIMITIVE; 315 | _number = value; 316 | } 317 | } 318 | 319 | private Uri Uri 320 | { 321 | get 322 | { 323 | Assert.IsTrue(_valueType == ValueTypes.STRING && _dataType == DataTypes.URI, 324 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.STRING or ValueTypes.URI." 325 | ); 326 | return new Uri(_string); 327 | } 328 | set 329 | { 330 | _valueType = ValueTypes.STRING; 331 | _dataType = DataTypes.URI; 332 | _string = value.OriginalString; 333 | } 334 | } 335 | 336 | private Guid Guid 337 | { 338 | get 339 | { 340 | Assert.IsTrue(_valueType == ValueTypes.STRING && _dataType == DataTypes.GUID, 341 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.STRING or ValueTypes.GUID." 342 | ); 343 | return Guid.Parse(_string); 344 | } 345 | set 346 | { 347 | _valueType = ValueTypes.STRING; 348 | _dataType = DataTypes.GUID; 349 | _string = value.ToString(); 350 | } 351 | } 352 | 353 | private DateTime DateTime 354 | { 355 | get 356 | { 357 | Assert.IsTrue(_valueType == ValueTypes.STRING && _dataType == DataTypes.DATE_TIME, 358 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.STRING or ValueTypes.DATE_TIME." 359 | ); 360 | return DateTime.SpecifyKind(DateTime.Parse(_string), DateTimeKind.Utc); 361 | } 362 | set 363 | { 364 | _valueType = ValueTypes.STRING; 365 | _dataType = DataTypes.DATE_TIME; 366 | _string = DateTime.SpecifyKind(value, DateTimeKind.Utc).ToString(CultureInfo.InvariantCulture); 367 | } 368 | } 369 | 370 | private DateTimeOffset DateTimeOffset 371 | { 372 | get 373 | { 374 | Assert.IsTrue(_valueType == ValueTypes.STRING && _dataType == DataTypes.DATE_TIME_OFFSET, 375 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.STRING or ValueTypes.DATE_TIME_OFFSET." 376 | ); 377 | return DateTimeOffset.Parse(_string); 378 | } 379 | set 380 | { 381 | _valueType = ValueTypes.STRING; 382 | _dataType = DataTypes.DATE_TIME_OFFSET; 383 | _string = value.ToString(CultureInfo.InvariantCulture); 384 | } 385 | } 386 | 387 | private TimeSpan TimeSpan 388 | { 389 | get 390 | { 391 | Assert.IsTrue(_valueType == ValueTypes.OBJECT && _dataType == DataTypes.TIME_SPAN, 392 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.OBJECT or ValueTypes.TIME_SPAN." 393 | ); 394 | return new TimeSpan((long)_number); 395 | } 396 | set 397 | { 398 | _valueType = ValueTypes.OBJECT; 399 | _dataType = DataTypes.TIME_SPAN; 400 | _number = value.Ticks; 401 | } 402 | } 403 | 404 | private Color Color 405 | { 406 | get 407 | { 408 | Assert.IsTrue(_valueType == ValueTypes.OBJECT && _dataType == DataTypes.COLOR, 409 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.OBJECT or ValueTypes.COLOR." 410 | ); 411 | return new Color(this["r"], this["g"], this["b"], this["a"]); 412 | } 413 | set 414 | { 415 | _valueType = ValueTypes.OBJECT; 416 | _dataType = DataTypes.COLOR; 417 | _container = new Dictionary { {"r", value.r}, {"g", value.g}, {"b", value.b}, {"a", value.a}}; 418 | } 419 | } 420 | 421 | private Color32 Color32 422 | { 423 | get 424 | { 425 | Assert.IsTrue(_valueType == ValueTypes.OBJECT && _dataType == DataTypes.COLOR, 426 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.OBJECT or ValueTypes.COLOR." 427 | ); 428 | return new Color(this["r"], this["g"], this["b"], this["a"]); 429 | } 430 | set 431 | { 432 | _valueType = ValueTypes.OBJECT; 433 | _dataType = DataTypes.COLOR; 434 | _container = new Dictionary { {"r", value.r}, {"g", value.g}, {"b", value.b}, {"a", value.a}}; 435 | } 436 | } 437 | 438 | private Vector2 Vector2 439 | { 440 | get 441 | { 442 | Assert.IsTrue(_valueType == ValueTypes.OBJECT && _dataType == DataTypes.VECTOR, 443 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.OBJECT or ValueTypes.VECTOR." 444 | ); 445 | return new Vector2(this["x"], this["y"]); 446 | } 447 | set 448 | { 449 | _valueType = ValueTypes.OBJECT; 450 | _dataType = DataTypes.VECTOR; 451 | _container = new Dictionary { {"x", value.x}, {"y", value.y}}; 452 | } 453 | } 454 | 455 | private Vector3 Vector3 456 | { 457 | get 458 | { 459 | Assert.IsTrue(_valueType == ValueTypes.OBJECT && _dataType == DataTypes.VECTOR, 460 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.OBJECT or ValueTypes.VECTOR." 461 | ); 462 | return new Vector3(this["x"], this["y"], this["z"]); 463 | } 464 | set 465 | { 466 | _valueType = ValueTypes.OBJECT; 467 | _dataType = DataTypes.VECTOR; 468 | _container = new Dictionary { {"x", value.x}, {"y", value.y}, {"z", value.z}}; 469 | } 470 | } 471 | 472 | private Vector4 Vector4 473 | { 474 | get 475 | { 476 | Assert.IsTrue(_valueType == ValueTypes.OBJECT && (_dataType == DataTypes.VECTOR || _dataType == DataTypes.QUATERNION), 477 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.OBJECT, ValueTypes.VECTOR or ValueTypes.QUATERNION." 478 | ); 479 | return new Vector4(this["x"], this["y"], this["z"], this["w"]); 480 | } 481 | set 482 | { 483 | _valueType = ValueTypes.OBJECT; 484 | _dataType = DataTypes.VECTOR; 485 | _container = new Dictionary { {"x", value.x}, {"y", value.y}, {"z", value.z}, {"w", value.w}}; 486 | } 487 | } 488 | 489 | private Quaternion Quaternion 490 | { 491 | get 492 | { 493 | Assert.IsTrue(_valueType == ValueTypes.OBJECT && (_dataType == DataTypes.VECTOR || _dataType == DataTypes.QUATERNION), 494 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.OBJECT, ValueTypes.VECTOR or ValueTypes.QUATERNION." 495 | ); 496 | return new Quaternion(this["x"], this["y"], this["z"], this["w"]); 497 | } 498 | set 499 | { 500 | _valueType = ValueTypes.OBJECT; 501 | _dataType = DataTypes.QUATERNION; 502 | _container = new Dictionary { {"x", value.x}, {"y", value.y}, {"z", value.z}, {"w", value.w}}; 503 | } 504 | } 505 | 506 | private Bounds Bounds 507 | { 508 | get 509 | { 510 | Assert.IsTrue(_valueType == ValueTypes.OBJECT && _dataType == DataTypes.BOUNDS, 511 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.OBJECT or ValueTypes.BOUNDS." 512 | ); 513 | return new Bounds(this["center"], this["size"]); 514 | } 515 | set 516 | { 517 | _valueType = ValueTypes.OBJECT; 518 | _dataType = DataTypes.BOUNDS; 519 | _container = new Dictionary { {"center", value.center}, {"size", value.size}}; 520 | } 521 | } 522 | 523 | private Rect Rect 524 | { 525 | get 526 | { 527 | Assert.IsTrue(_valueType == ValueTypes.OBJECT && _dataType == DataTypes.RECT, 528 | $"Assertion failed: _valueType is {_valueType}, but expected ValueTypes.OBJECT or ValueTypes.RECT." 529 | ); 530 | return new Rect(this["x"], this["y"], this["width"], this["height"]); 531 | } 532 | set 533 | { 534 | _valueType = ValueTypes.OBJECT; 535 | _dataType = DataTypes.RECT; 536 | _container = new Dictionary { {"x", value.x}, {"y", value.y}, {"width", value.width}, {"height", value.height}}; 537 | } 538 | } 539 | 540 | #endregion 541 | 542 | #region Constructors 543 | 544 | public Value() {} 545 | 546 | private Value(ValueTypes valueType, DataTypes dataTypes) 547 | { 548 | _valueType = valueType; 549 | _dataType = dataTypes; 550 | } 551 | 552 | public Value(string value) { String = value; } 553 | public Value(bool value) { Bool = value; } 554 | public Value(double value) { Number = value; } 555 | public Value(Uri value) { Uri = value; } 556 | public Value(Guid value) { Guid = value; } 557 | public Value(DateTime value) { DateTime = value; } 558 | public Value(DateTimeOffset value) { DateTimeOffset = value; } 559 | public Value(TimeSpan value) { TimeSpan = value; } 560 | public Value(Color value) { Color = value; } 561 | public Value(Color32 value) { Color32 = value; } 562 | public Value(Vector2 value) { Vector2 = value; } 563 | public Value(Vector3 value) { Vector3 = value; } 564 | public Value(Vector4 value) { Vector4 = value; } 565 | public Value(Quaternion value) { Quaternion = value; } 566 | public Value(Bounds value) { Bounds = value; } 567 | public Value(Rect value) { Rect = value; } 568 | 569 | public Value(IEnumerable data) 570 | { 571 | _valueType = ValueTypes.ARRAY; 572 | _dataType = DataTypes.CONTAINER; 573 | _array = new List(data); 574 | } 575 | 576 | public Value(IDictionary data) 577 | { 578 | _valueType = ValueTypes.OBJECT; 579 | _dataType = DataTypes.CONTAINER; 580 | _container = new Dictionary(data); 581 | } 582 | 583 | public static Value Null => new Value(ValueTypes.NULL, DataTypes.PRIMITIVE); 584 | public static Value Array => new Value(ValueTypes.ARRAY, DataTypes.CONTAINER); 585 | public static Value Object => new Value(ValueTypes.OBJECT, DataTypes.CONTAINER); 586 | 587 | #endregion 588 | 589 | #region ToJsonType 590 | 591 | public static implicit operator Value(string value) => new Value(value); 592 | public static implicit operator Value(string[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 593 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 594 | public static implicit operator Value(bool value) => new Value(value); 595 | public static implicit operator Value(bool[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 596 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 597 | public static implicit operator Value(float value) => new Value((double)(decimal)value); 598 | public static implicit operator Value(float[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 599 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 600 | public static implicit operator Value(double value) => new Value(value); 601 | public static implicit operator Value(double[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 602 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 603 | public static implicit operator Value(decimal value) => new Value((double)value); 604 | public static implicit operator Value(decimal[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 605 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 606 | public static implicit operator Value(short value) => new Value(value); 607 | public static implicit operator Value(short[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 608 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 609 | public static implicit operator Value(int value) => new Value(value); 610 | public static implicit operator Value(int[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 611 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 612 | public static implicit operator Value(long value) => new Value(value); 613 | public static implicit operator Value(long[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 614 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 615 | public static implicit operator Value(ushort value) => new Value(value); 616 | public static implicit operator Value(ushort[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 617 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 618 | public static implicit operator Value(uint value) => new Value(value); 619 | public static implicit operator Value(uint[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 620 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 621 | public static implicit operator Value(ulong value) => new Value(value); 622 | public static implicit operator Value(ulong[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 623 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 624 | public static implicit operator Value(sbyte value) => new Value(value); 625 | public static implicit operator Value(sbyte[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 626 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 627 | public static implicit operator Value(byte value) => new Value(value); 628 | public static implicit operator Value(byte[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 629 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 630 | 631 | public static implicit operator Value(Uri value) => new Value(value); 632 | public static implicit operator Value(Uri[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 633 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 634 | public static implicit operator Value(Guid value) => new Value(value); 635 | public static implicit operator Value(Guid[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 636 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 637 | public static implicit operator Value(DateTime value) => new Value(value); 638 | public static implicit operator Value(DateTime[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 639 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 640 | public static implicit operator Value(DateTimeOffset value) => new Value(value); 641 | public static implicit operator Value(DateTimeOffset[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 642 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 643 | public static implicit operator Value(TimeSpan value) => new Value(value); 644 | public static implicit operator Value(TimeSpan[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 645 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 646 | public static implicit operator Value(Color value) => new Value(value); 647 | public static implicit operator Value(Color[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 648 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 649 | public static implicit operator Value(Color32 value) => new Value(value); 650 | public static implicit operator Value(Color32[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 651 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 652 | public static implicit operator Value(Vector2 value) => new Value(value); 653 | public static implicit operator Value(Vector2[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 654 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 655 | public static implicit operator Value(Vector3 value) => new Value(value); 656 | public static implicit operator Value(Vector3[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 657 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 658 | public static implicit operator Value(Vector4 value) => new Value(value); 659 | public static implicit operator Value(Vector4[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 660 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 661 | public static implicit operator Value(Quaternion value) => new Value(value); 662 | public static implicit operator Value(Quaternion[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 663 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 664 | public static implicit operator Value(Bounds value) => new Value(value); 665 | public static implicit operator Value(Bounds[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 666 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 667 | public static implicit operator Value(Rect value) => new Value(value); 668 | public static implicit operator Value(Rect[] value) => new Value(System.Array.ConvertAll(value, x => (Value)x)); 669 | public static implicit operator Value(List value) => new Value(System.Array.ConvertAll(value.ToArray(), x => (Value)x)); 670 | 671 | #endregion 672 | 673 | #region ToOtherTypes 674 | 675 | public static implicit operator string(Value value) => value.String; 676 | public static implicit operator string[](Value value) => value._array.ConvertAll(x => (string)x).ToArray(); 677 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (string)x); 678 | public static implicit operator bool(Value value) => value.Bool; 679 | public static implicit operator bool[](Value value) => value._array.ConvertAll(x => (bool)x).ToArray(); 680 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (bool)x); 681 | public static implicit operator float(Value value) => (float)value.Number; 682 | public static implicit operator float[](Value value) => value._array.ConvertAll(x => (float)x).ToArray(); 683 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (float)x); 684 | public static implicit operator double(Value value) => value.Number; 685 | public static implicit operator double[](Value value) => value._array.ConvertAll(x => (double)x).ToArray(); 686 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (double)x); 687 | public static implicit operator decimal(Value value) => (decimal)value.Number; 688 | public static implicit operator decimal[](Value value) => value._array.ConvertAll(x => (decimal)x).ToArray(); 689 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (decimal)x); 690 | public static implicit operator short(Value value) => (short)value.Number; 691 | public static implicit operator short[](Value value) => value._array.ConvertAll(x => (short)x).ToArray(); 692 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (short)x); 693 | public static implicit operator int(Value value) => (int)value.Number; 694 | public static implicit operator int[](Value value) => value._array.ConvertAll(x => (int)x).ToArray(); 695 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (int)x); 696 | public static implicit operator long(Value value) => (long)value.Number; 697 | public static implicit operator long[](Value value) => value._array.ConvertAll(x => (long)x).ToArray(); 698 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (long)x); 699 | public static implicit operator ushort(Value value) => (ushort)value.Number; 700 | public static implicit operator ushort[](Value value) => value._array.ConvertAll(x => (ushort)x).ToArray(); 701 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (ushort)x); 702 | public static implicit operator uint(Value value) => (uint)value.Number; 703 | public static implicit operator uint[](Value value) => value._array.ConvertAll(x => (uint)x).ToArray(); 704 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (uint)x); 705 | public static implicit operator ulong(Value value) => (ulong)value.Number; 706 | public static implicit operator ulong[](Value value) => value._array.ConvertAll(x => (ulong)x).ToArray(); 707 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (ulong)x); 708 | public static implicit operator sbyte(Value value) => (sbyte)value.Number; 709 | public static implicit operator sbyte[](Value value) => value._array.ConvertAll(x => (sbyte)x).ToArray(); 710 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (sbyte)x); 711 | public static implicit operator byte(Value value) => (byte)value.Number; 712 | public static implicit operator byte[](Value value) => value._array.ConvertAll(x => (byte)x).ToArray(); 713 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (byte)x); 714 | 715 | public static implicit operator Uri(Value value) => value.Uri; 716 | public static implicit operator Uri[](Value value) => value._array.ConvertAll(x => (Uri)x).ToArray(); 717 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (Uri)x); 718 | public static implicit operator Guid(Value value) => value.Guid; 719 | public static implicit operator Guid[](Value value) => value._array.ConvertAll(x => (Guid)x).ToArray(); 720 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (Guid)x); 721 | public static implicit operator DateTime(Value value) => value.DateTime; 722 | public static implicit operator DateTime[](Value value) => value._array.ConvertAll(x => (DateTime)x).ToArray(); 723 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (DateTime)x); 724 | public static implicit operator DateTimeOffset(Value value) => value.DateTimeOffset; 725 | public static implicit operator DateTimeOffset[](Value value) => value._array.ConvertAll(x => (DateTimeOffset)x).ToArray(); 726 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (DateTimeOffset)x); 727 | public static implicit operator TimeSpan(Value value) => value.TimeSpan; 728 | public static implicit operator TimeSpan[](Value value) => value._array.ConvertAll(x => (TimeSpan)x).ToArray(); 729 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (TimeSpan)x); 730 | public static implicit operator Color(Value value) => value.Color; 731 | public static implicit operator Color[](Value value) => value._array.ConvertAll(x => (Color)x).ToArray(); 732 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (Color)x); 733 | public static implicit operator Color32(Value value) => value.Color32; 734 | public static implicit operator Color32[](Value value) => value._array.ConvertAll(x => (Color32)x).ToArray(); 735 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (Color32)x); 736 | public static implicit operator Vector2(Value value) => value.Vector2; 737 | public static implicit operator Vector2[](Value value) => value._array.ConvertAll(x => (Vector2)x).ToArray(); 738 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (Vector2)x); 739 | public static implicit operator Vector3(Value value) => value.Vector3; 740 | public static implicit operator Vector3[](Value value) => value._array.ConvertAll(x => (Vector3)x).ToArray(); 741 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (Vector3)x); 742 | public static implicit operator Vector4(Value value) => value.Vector4; 743 | public static implicit operator Vector4[](Value value) => value._array.ConvertAll(x => (Vector4)x).ToArray(); 744 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (Vector4)x); 745 | public static implicit operator Quaternion(Value value) => value.Quaternion; 746 | public static implicit operator Quaternion[](Value value) => value._array.ConvertAll(x => (Quaternion)x).ToArray(); 747 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (Quaternion)x); 748 | public static implicit operator Bounds(Value value) => value.Bounds; 749 | public static implicit operator Bounds[](Value value) => value._array.ConvertAll(x => (Bounds)x).ToArray(); 750 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (Bounds)x); 751 | public static implicit operator Rect(Value value) => value.Rect; 752 | public static implicit operator Rect[](Value value) => value._array.ConvertAll(x => (Rect)x).ToArray(); 753 | public static implicit operator List(Value value) => value._array.ConvertAll(x => (Rect)x); 754 | 755 | #endregion 756 | 757 | #region Writer 758 | 759 | private void Write(StringWriter writer, bool includeTypeInfo = false) 760 | { 761 | if (includeTypeInfo) 762 | { 763 | writer.Write("{"); 764 | writer.Write($"\"JsonType\": \"{_valueType}\", \"DataType\": \"{_dataType}\", \"Value\": "); 765 | } 766 | switch (_valueType) 767 | { 768 | case ValueTypes.UNDEFINED: 769 | case ValueTypes.NULL: 770 | writer.Write("null"); 771 | break; 772 | case ValueTypes.STRING: 773 | writer.Write("\""); 774 | writer.Write(SanitizeStringForJson(_string)); 775 | writer.Write("\""); 776 | break; 777 | case ValueTypes.BOOLEAN: 778 | writer.Write(_bool ? "true" : "false"); 779 | break; 780 | case ValueTypes.NUMBER: 781 | writer.Write(_number.ToString(CultureInfo.InvariantCulture)); 782 | break; 783 | case ValueTypes.ARRAY: 784 | writer.Write("["); 785 | int arrayIndex = 0; 786 | int arrayCount = _array.Count - 1; 787 | foreach (Value item in _array) 788 | { 789 | item.Write(writer, includeTypeInfo); 790 | if (arrayIndex < arrayCount) writer.Write(", "); 791 | arrayIndex++; 792 | } 793 | writer.Write("]"); 794 | break; 795 | case ValueTypes.OBJECT: 796 | writer.Write("{"); 797 | if (_dataType == DataTypes.CONTAINER) 798 | { 799 | int containerIndex = 0; 800 | int containerCount = _container.Count - 1; 801 | foreach (KeyValuePair kvp in _container) 802 | { 803 | writer.Write($"\"{kvp.Key}\": "); 804 | kvp.Value.Write(writer, includeTypeInfo); 805 | if (containerIndex < containerCount) writer.Write(", "); 806 | containerIndex++; 807 | } 808 | } 809 | else 810 | { 811 | int containerIndex = 0; 812 | int containerCount = _container.Count - 1; 813 | foreach (KeyValuePair kvp in _container) 814 | { 815 | writer.Write($"\"{kvp.Key}\": {kvp.Value}"); 816 | if (containerIndex < containerCount) writer.Write(", "); 817 | containerIndex++; 818 | } 819 | } 820 | writer.Write("}"); 821 | break; 822 | default: 823 | throw new ArgumentOutOfRangeException(); 824 | } 825 | 826 | if (includeTypeInfo) 827 | { 828 | writer.Write("}"); 829 | } 830 | } 831 | 832 | #endregion 833 | 834 | #region Reader 835 | 836 | public static string SanitizeStringForJson(string s) 837 | { 838 | if (s == null || s.Length == 0) { 839 | return ""; 840 | } 841 | 842 | StringBuilder sb = new StringBuilder(); 843 | for (int i = 0; i < s.Length; i += 1) { 844 | char c = s[i]; 845 | if (c >= 0 && c <= 7 || c == 11 || c >= 14 && c <= 31 || c == 39 || c == 60 || c == 62) 846 | sb.AppendFormat("\\u{0:x4}", (int)c); 847 | else switch (c) { 848 | case '\\': 849 | case '"': 850 | sb.Append('\\'); 851 | sb.Append(c); 852 | break; 853 | case '\b': 854 | sb.Append("\\b"); 855 | break; 856 | case '\t': 857 | sb.Append("\\t"); 858 | break; 859 | case '\n': 860 | sb.Append("\\n"); 861 | break; 862 | case '\f': 863 | sb.Append("\\f"); 864 | break; 865 | case '\r': 866 | sb.Append("\\r"); 867 | break; 868 | default: 869 | sb.Append(c); 870 | break; 871 | } 872 | } 873 | return sb.ToString(); 874 | } 875 | 876 | #endregion 877 | 878 | #region UnitySerialization 879 | public void OnBeforeSerialize() 880 | { 881 | if (IsArray) SerializeList(); 882 | if (IsObject) SerializeDictionary(); 883 | } 884 | 885 | private void SerializeList() 886 | { 887 | if (_array == null) _array = new List(0); 888 | int count = _array.Count; 889 | _arrayData = new string[count]; 890 | if (count <= 0) return; 891 | for (int i = 0; i < count; i++) 892 | { 893 | _arrayData[i] = JsonUtility.ToJson(_array[i]); 894 | } 895 | } 896 | 897 | private void SerializeDictionary() 898 | { 899 | if (_container == null) _container = new Dictionary(0); 900 | int count = _container.Count; 901 | _containerKeys = new string[count]; 902 | _containerValues = new string[count]; 903 | if (count <= 0) return; 904 | using (Dictionary.Enumerator e = _container.GetEnumerator()) 905 | { 906 | for (int i = 0; i < count; i++) 907 | { 908 | e.MoveNext(); 909 | _containerKeys[i] = e.Current.Key; 910 | _containerValues[i] = JsonUtility.ToJson(e.Current.Value); 911 | } 912 | } 913 | } 914 | 915 | public void OnAfterDeserialize() 916 | { 917 | if (IsArray) DeserializeList(); 918 | if (IsObject) DeserializeDictionary(); 919 | } 920 | 921 | private void DeserializeList() 922 | { 923 | if (_arrayData == null) return; 924 | int count = _arrayData.Length; 925 | _array = new List(count); 926 | if (count == 0) return; 927 | foreach (string data in _arrayData) 928 | { 929 | Value item = new Value(); 930 | JsonUtility.FromJsonOverwrite(data, item); 931 | _array.Add(item); 932 | } 933 | } 934 | 935 | private void DeserializeDictionary() 936 | { 937 | if (_containerKeys == null) return; 938 | int count = _containerKeys.Length; 939 | _container = new Dictionary(count); 940 | if (count == 0) return; 941 | for (int i = 0; i < count; i++) 942 | { 943 | Value item = new Value(); 944 | JsonUtility.FromJsonOverwrite(_containerValues[i], item); 945 | _container[_containerKeys[i]] = item; 946 | } 947 | } 948 | #endregion 949 | 950 | // Only used to migrate from 1.X to 2.X - Will be removed soon 951 | #region Deserialize 952 | 953 | public static Value Deserialize(string json) 954 | { 955 | return ParseValue(new StringReader(json)); 956 | } 957 | 958 | private static Value ParseValue(StringReader reader) 959 | { 960 | return ParseByToken(reader, NextToken(reader)); 961 | } 962 | 963 | private static Value ParseByToken(StringReader reader, Token token) 964 | { 965 | switch (token) { 966 | case Token.STRING: 967 | return ParseString(reader); 968 | case Token.NUMBER: 969 | return ParseNumber(reader); 970 | case Token.CURLY_OPEN: 971 | // ditch opening brace 972 | reader.Read(); 973 | return ParseObject(reader); 974 | case Token.SQUARED_OPEN: 975 | // ditch opening bracket 976 | reader.Read(); 977 | return ParseArray(reader); 978 | case Token.TRUE: 979 | return true; 980 | case Token.FALSE: 981 | return false; 982 | case Token.NULL: 983 | return Value.Null; 984 | default: 985 | return Value.Null; 986 | } 987 | } 988 | 989 | private static string ParseString(StringReader reader) 990 | { 991 | StringBuilder s = new StringBuilder(); 992 | 993 | // ditch opening quote 994 | reader.Read(); 995 | 996 | bool parsing = true; 997 | while (parsing) { 998 | 999 | if (reader.Peek() == -1) { 1000 | parsing = false; 1001 | break; 1002 | } 1003 | 1004 | char c = NextChar(reader); 1005 | switch (c) { 1006 | case '"': 1007 | parsing = false; 1008 | break; 1009 | case '\\': 1010 | if (reader.Peek() == -1) { 1011 | parsing = false; 1012 | break; 1013 | } 1014 | 1015 | c = NextChar(reader); 1016 | switch (c) { 1017 | case '"': 1018 | case '\\': 1019 | case '/': 1020 | s.Append(c); 1021 | break; 1022 | case 'b': 1023 | s.Append('\b'); 1024 | break; 1025 | case 'f': 1026 | s.Append('\f'); 1027 | break; 1028 | case 'n': 1029 | s.Append('\n'); 1030 | break; 1031 | case 'r': 1032 | s.Append('\r'); 1033 | break; 1034 | case 't': 1035 | s.Append('\t'); 1036 | break; 1037 | case 'u': 1038 | StringBuilder hex = new StringBuilder(); 1039 | 1040 | for (int i=0; i< 4; i++) { 1041 | hex.Append(NextChar(reader)); 1042 | } 1043 | 1044 | s.Append((char) Convert.ToInt32(hex.ToString(), 16)); 1045 | break; 1046 | } 1047 | break; 1048 | default: 1049 | s.Append(c); 1050 | break; 1051 | } 1052 | } 1053 | 1054 | return s.ToString(); 1055 | } 1056 | 1057 | private static double ParseNumber(StringReader reader) 1058 | { 1059 | string number = NextWord(reader); 1060 | double parsedDouble; 1061 | double.TryParse(number, out parsedDouble); 1062 | return parsedDouble; 1063 | } 1064 | 1065 | private static Value ParseArray(StringReader reader) 1066 | { 1067 | List array = new List(); 1068 | 1069 | bool parsing = true; 1070 | while (parsing) { 1071 | Token nextToken = NextToken(reader); 1072 | 1073 | switch (nextToken) { 1074 | case Token.NONE: 1075 | return null; 1076 | case Token.COMMA: 1077 | continue; 1078 | case Token.SQUARED_CLOSE: 1079 | parsing = false; 1080 | break; 1081 | default: 1082 | array.Add(ParseByToken(reader, nextToken)); 1083 | break; 1084 | } 1085 | } 1086 | 1087 | return new Value(array); 1088 | } 1089 | 1090 | private static Value ParseObject(StringReader reader) 1091 | { 1092 | Dictionary data = new Dictionary(); 1093 | 1094 | while (true) { 1095 | switch (NextToken(reader)) { 1096 | case Token.NONE: 1097 | return null; 1098 | case Token.COMMA: 1099 | continue; 1100 | case Token.CURLY_CLOSE: 1101 | if (data.ContainsKey("JsonType") && data.ContainsKey("DataType")) 1102 | { 1103 | return FromSerialization(data["JsonType"], data["DataType"], data["Value"]); 1104 | } 1105 | return new Value(data); 1106 | default: 1107 | // key 1108 | string key = ParseString(reader); 1109 | if (key == null) { 1110 | return null; 1111 | } 1112 | // : 1113 | if (NextToken(reader) != Token.COLON) { 1114 | return null; 1115 | } 1116 | // ditch the colon 1117 | reader.Read(); 1118 | 1119 | // value 1120 | data[key] = ParseValue(reader); 1121 | break; 1122 | } 1123 | } 1124 | } 1125 | 1126 | private const string WhiteSpace = " \t\n\r"; 1127 | private const string WordBreak = " \t\n\r{}[],:\""; 1128 | 1129 | private enum Token 1130 | { 1131 | NONE, 1132 | CURLY_OPEN, 1133 | CURLY_CLOSE, 1134 | SQUARED_OPEN, 1135 | SQUARED_CLOSE, 1136 | COLON, 1137 | COMMA, 1138 | STRING, 1139 | NUMBER, 1140 | TRUE, 1141 | FALSE, 1142 | NULL 1143 | }; 1144 | 1145 | private static char PeekChar(StringReader reader) => Convert.ToChar(reader.Peek()); 1146 | 1147 | private static char NextChar(StringReader reader) => Convert.ToChar(reader.Read()); 1148 | 1149 | private static string NextWord(StringReader reader) 1150 | { 1151 | StringBuilder word = new StringBuilder(); 1152 | while (WordBreak.IndexOf(PeekChar(reader)) == -1) { 1153 | word.Append(NextChar(reader)); 1154 | 1155 | if (reader.Peek() == -1) { 1156 | break; 1157 | } 1158 | } 1159 | return word.ToString(); 1160 | } 1161 | 1162 | private static void EatWhitespace(StringReader reader) 1163 | { 1164 | while (WhiteSpace.IndexOf(PeekChar(reader)) != -1) { 1165 | reader.Read(); 1166 | 1167 | if (reader.Peek() == -1) { 1168 | break; 1169 | } 1170 | } 1171 | } 1172 | 1173 | private static Token NextToken(StringReader reader) 1174 | { 1175 | EatWhitespace(reader); 1176 | 1177 | if (reader.Peek() == -1) { 1178 | return Token.NONE; 1179 | } 1180 | 1181 | char c = PeekChar(reader); 1182 | switch (c) { 1183 | case '{': 1184 | return Token.CURLY_OPEN; 1185 | case '}': 1186 | reader.Read(); 1187 | return Token.CURLY_CLOSE; 1188 | case '[': 1189 | return Token.SQUARED_OPEN; 1190 | case ']': 1191 | reader.Read(); 1192 | return Token.SQUARED_CLOSE; 1193 | case ',': 1194 | reader.Read(); 1195 | return Token.COMMA; 1196 | case '"': 1197 | return Token.STRING; 1198 | case ':': 1199 | return Token.COLON; 1200 | case '0': 1201 | case '1': 1202 | case '2': 1203 | case '3': 1204 | case '4': 1205 | case '5': 1206 | case '6': 1207 | case '7': 1208 | case '8': 1209 | case '9': 1210 | case '-': 1211 | return Token.NUMBER; 1212 | } 1213 | 1214 | string word = NextWord(reader); 1215 | 1216 | switch (word) { 1217 | case "false": 1218 | return Token.FALSE; 1219 | case "true": 1220 | return Token.TRUE; 1221 | case "null": 1222 | return Token.NULL; 1223 | } 1224 | 1225 | return Token.NONE; 1226 | } 1227 | 1228 | private static Value FromSerialization(Value valueType, Value dataType, Value value) 1229 | { 1230 | value._valueType = (ValueTypes) Enum.Parse(typeof(ValueTypes), valueType); 1231 | value._dataType = (DataTypes) Enum.Parse(typeof(DataTypes), dataType); 1232 | return value; 1233 | } 1234 | #endregion 1235 | 1236 | } 1237 | } 1238 | -------------------------------------------------------------------------------- /Mixpanel/Value.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9ee33eb51543748cc9399b6b7911c181 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Mixpanel/Worker.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.Networking; 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Net; 9 | using System.Net.Http; 10 | using System.Web; 11 | 12 | namespace mixpanel 13 | { 14 | 15 | internal static class Util 16 | { 17 | internal static double CurrentTimeInSeconds() 18 | { 19 | DateTime epochStart = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); 20 | double currentEpochTime = (DateTime.UtcNow - epochStart).TotalSeconds; 21 | return currentEpochTime; 22 | } 23 | 24 | internal static double CurrentTimeInMilliseconds() 25 | { 26 | DateTime epochStart = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); 27 | long currentEpochTime = (long)(DateTime.UtcNow - epochStart).TotalMilliseconds; 28 | return currentEpochTime; 29 | } 30 | 31 | internal static string CurrentDateTime() 32 | { 33 | return DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss"); 34 | } 35 | 36 | internal static string GetRadio() 37 | { 38 | switch(Application.internetReachability) 39 | { 40 | case NetworkReachability.NotReachable : 41 | return "none"; 42 | case NetworkReachability.ReachableViaCarrierDataNetwork : 43 | return "carrier"; 44 | case NetworkReachability.ReachableViaLocalAreaNetwork : 45 | return "wifi"; 46 | } 47 | return "none"; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Mixpanel/Worker.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 684af6ef11682493fa052ce1133413a8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | Mixpanel Unity SDK 3 |
4 | 5 | # Table of Contents 6 | 7 | 8 | 9 | - [Introduction](#introduction) 10 | - [Quick Start Guide](#quick-start-guide) 11 | - [Install Mixpanel](#1-install-mixpanel) 12 | - [Initialize Mixpanel](#2-initialize-mixpanel) 13 | - [Send Data](#3-send-data) 14 | - [Check for Success](#4-check-for-success) 15 | - [FAQ](#faq) 16 | - [I want to know more!](#i-want-to-know-more) 17 | 18 | 19 | 20 | # Overview 21 | 22 | Welcome to the official Mixpanel Unity SDK. The Mixpanel Unity SDK is an open-source project, and we'd love to see your contributions! 23 | 24 | Check out our [official documentation](https://mixpanel.com/help/reference/unity) to learn how to make use of all the features we currently support! 25 | 26 | # Quick Start Guide 27 | 28 | Supported Unity Version >= 2018.3. For older versions, you need to have `.NET 4.x Equivalent` selected as the scripting runtime version in your editor settings. 29 | 30 | ## 1. Install Mixpanel 31 | 32 | This library can be installed using the unity package manager system with git. We support Unity 2018.3 and above. For older versions of Unity, you need to have .NET 4.x Equivalent selected as the scripting runtime version in your editor settings. 33 | 34 | - In your unity project root open ./Packages/manifest.json 35 | - Add the following line to the dependencies section "com.mixpanel.unity": "https://github.com/mixpanel/mixpanel-unity.git#master", or point to a specific version. Example: ("com.mixpanel.unity": "https://github.com/mixpanel/mixpanel-unity.git#v3.5.1") 36 | - Open Unity and the package should download automatically 37 | Alternatively you can go to the [releases page](https://github.com/mixpanel/mixpanel-unity/releases) and download the .unitypackage file and have unity install that. 38 | 39 | ## 2. Initialize Mixpanel 40 | 41 | You will need your project token for initializing your library. You can get your project token from [project settings](https://mixpanel.com/settings/project). 42 | To initialize the library, first open the unity project settings menu for Mixpanel. (Edit -> Project Settings -> Mixpanel) Then, enter your project token into the Token and Debug Token input fields within the inspector. Please note if you prefer to initialize Mixpanel manually, you can select the `Manual Initialization` in the settings and call `Mixpanel.Init()` to initialize. 43 | 44 | ![unity_screenshots](https://user-images.githubusercontent.com/36679208/152408022-62440f50-04c7-4ff3-b331-02d3d3122c9e.jpg) 45 | 46 | ## 3. Send Data 47 | 48 | Let's get started by sending event data. You can send an event from anywhere in your application. Better understand user behavior by storing details that are specific to the event (properties). 49 | 50 | ```csharp 51 | using mixpanel; 52 | // Track with event-name 53 | Mixpanel.Track("Sent Message"); 54 | // Track with event-name and property 55 | var props = new Value(); 56 | props["Plan"] = "Premium"; 57 | Mixpanel.Track("Plan Selected", props); 58 | ``` 59 | 60 | ## 4. Check for Success 61 | 62 | [Open up Events in Mixpanel](http://mixpanel.com/report/events) to view incoming events. 63 | Once data hits our API, it generally takes ~60 seconds for it to be processed, stored, and queryable in your project. 64 | 65 | 👋 👋 Tell us about the Mixpanel developer experience! [https://www.mixpanel.com/devnps](https://www.mixpanel.com/devnps) 👍 👎 66 | 67 | # FAQ 68 | 69 | **I want to stop tracking an event/event property in Mixpanel. Is that possible?** 70 | 71 | Yes, in Lexicon, you can intercept and drop incoming events or properties. Mixpanel won’t store any new data for the event or property you select to drop. [See this article for more information](https://help.mixpanel.com/hc/en-us/articles/360001307806#dropping-events-and-properties). 72 | 73 | **I have a test user I would like to opt out of tracking. How do I do that?** 74 | 75 | Mixpanel’s client-side tracking library contains the OptOutTracking() method, which will set the user’s local opt-out state to “true” and will prevent data from being sent from a user’s device. More detailed instructions can be found in the section. 76 | 77 | **Starting with iOS 14.5, do I need to request the user’s permission through the AppTrackingTransparency framework to use Mixpanel?** 78 | 79 | No, Mixpanel does not use IDFA so it does not require user permission through the AppTrackingTransparency(ATT) framework. 80 | 81 | **If I use Mixpanel, how do I answer app privacy questions for the App Store?** 82 | 83 | Please refer to our [Apple App Developer Privacy Guidance](https://mixpanel.com/legal/app-store-privacy-details/) 84 | 85 | # I want to know more! 86 | 87 | No worries, here are some links that you will find useful: 88 | 89 | - **[Full Documentation](https://developer.mixpanel.com/docs/unity)** 90 | - **[Full API Reference](http://mixpanel.github.io/mixpanel-unity/api-reference/annotated.html)** 91 | 92 | [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/mixpanel/mixpanel-unity) 93 | 94 | Have any questions? Reach out to Mixpanel [Support](https://help.mixpanel.com/hc/en-us/requests/new) to speak to someone smart, quickly. 95 | 96 | ## Examples 97 | 98 | Checkout our Examples by importing the `Examples.unitypackage` file located inside the `Mixpanel` folder after you follow the installation instructions above 99 | 100 | ## Changelog 101 | 102 | See [changelog](https://github.com/mixpanel/mixpanel-unity/tree/master/CHANGELOG.md) for details. 103 | 104 | ## Want to Contribute? 105 | 106 | The Mixpanel library for Unity is an open source project, and we'd love to see your contributions! 107 | We'd also love for you to come and work with us! Check out our **[open positions](https://mixpanel.com/jobs/#openings)** for details. 108 | 109 | The best way to work on the Mixpanel library is the clone this repository and use a unity "local" package reference by creating a new unity project and opening the `./Packages/manifest.json` file and adding the following line under the `dependencies` section 110 | 111 | ```json 112 | "com.mixpanel.unity": "file:C:/Path/to/cloned/repo/mixpanel-unity", 113 | ``` 114 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bc09158c44906844e95da5445b1e7df4 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Tests.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-unity/aa28e6eeefe01ef98c6ca53b2551394bc6660ec2/Tests.unitypackage -------------------------------------------------------------------------------- /Tests.unitypackage.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ed5c5ae2fddcfe84d95cae5c65febbfb 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.mixpanel.unity", 3 | "displayName": "Mixpanel", 4 | "version": "3.5.3", 5 | "description": "Official Mixpanel library for Unity.", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/mixpanel/mixpanel-unity" 9 | }, 10 | "author": "Mixpanel", 11 | "license": "Apache V2", 12 | "keywords": [ 13 | "unity3d", 14 | "unity3d-package", 15 | "analytics", 16 | "mixpanel" 17 | ], 18 | "category": "Mixpanel" 19 | } -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8ad1529f0392cd643b676df17e4a2a1f 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------