├── .github └── workflows │ ├── iOS.yml │ ├── macOS.yml │ └── release.yml ├── .gitignore ├── .hound.yml ├── .swiftlint.yml ├── CHANGELOG.md ├── Info.plist ├── LICENSE ├── Mixpanel-swift.podspec ├── Mixpanel.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── Mixpanel.xcscheme │ ├── Mixpanel_macOS.xcscheme │ ├── Mixpanel_tvOS.xcscheme │ └── Mixpanel_watchOS.xcscheme ├── MixpanelDemo ├── .gitignore ├── MixpanelDemo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ ├── MixpanelDemo.xcscheme │ │ ├── MixpanelDemoWatch (Notification).xcscheme │ │ └── MixpanelDemoWatch.xcscheme ├── MixpanelDemo │ ├── ActionCompleteViewController.swift │ ├── AppDelegate.swift │ ├── AppIcon 1024x1024.png │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── 100.png │ │ │ ├── 1024.png │ │ │ ├── 114.png │ │ │ ├── 120.png │ │ │ ├── 144.png │ │ │ ├── 152.png │ │ │ ├── 167.png │ │ │ ├── 180.png │ │ │ ├── 20.png │ │ │ ├── 29.png │ │ │ ├── 40.png │ │ │ ├── 50.png │ │ │ ├── 57.png │ │ │ ├── 58.png │ │ │ ├── 60.png │ │ │ ├── 72.png │ │ │ ├── 76.png │ │ │ ├── 80.png │ │ │ ├── 87.png │ │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── GDPRViewController.swift │ ├── GroupsViewController.swift │ ├── Info.plist │ ├── LoginViewController.swift │ ├── MixpanelDemo.entitlements │ ├── PeopleViewController.swift │ ├── TrackingViewController.swift │ └── UtilityViewController.swift ├── MixpanelDemoMac │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ └── Main.storyboard │ ├── Info.plist │ ├── MixpanelDemoMac.entitlements │ └── ViewController.swift ├── MixpanelDemoMacTests │ ├── Info.plist │ ├── MixpanelBaseTests.swift │ ├── MixpanelDemoMacTests-Bridging-Header.h │ ├── MixpanelDemoTests.swift │ ├── MixpanelGroupTests.swift │ ├── MixpanelOptOutTests.swift │ ├── MixpanelPeopleTests.swift │ └── TestConstants.swift ├── MixpanelDemoMacUITests │ ├── Info.plist │ └── MixpanelDemoMacUITests.swift ├── MixpanelDemoTV │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── App Icon & Top Shelf Image.brandassets │ │ │ ├── App Icon - App Store.imagestack │ │ │ │ ├── Back.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ ├── Front.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ └── Middle.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ ├── App Icon.imagestack │ │ │ │ ├── Back.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ ├── Front.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ │ └── Middle.imagestacklayer │ │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Top Shelf Image Wide.imageset │ │ │ │ └── Contents.json │ │ │ └── Top Shelf Image.imageset │ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── Launch Image.launchimage │ │ │ └── Contents.json │ ├── Base.lproj │ │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift ├── MixpanelDemoTVTests │ ├── Info.plist │ └── MixpanelDemoTVTests.swift ├── MixpanelDemoTVUITests │ ├── Info.plist │ └── MixpanelDemoTVUITests.swift ├── MixpanelDemoTests │ ├── Assets.xcassets │ │ ├── Contents.json │ │ └── checkerboard.imageset │ │ │ ├── Contents.json │ │ │ └── checkerboard.jpg │ ├── Info.plist │ ├── JSONHandlerTests.swift │ ├── LoggerTests.swift │ ├── MixpanelAutomaticEventsTests.swift │ ├── MixpanelBaseTests.swift │ ├── MixpanelDemoTests.swift │ ├── MixpanelFeatureFlagTests.swift │ ├── MixpanelGroupTests.swift │ ├── MixpanelOptOutTests.swift │ ├── MixpanelPeopleTests.swift │ ├── TestConstants.swift │ ├── mixpanel-testToken-events │ ├── mixpanel-testToken-groups │ ├── mixpanel-testToken-optOutStatus │ ├── mixpanel-testToken-people │ └── mixpanel-testToken-properties ├── MixpanelDemoWatch Extension │ ├── Assets.xcassets │ │ ├── Complication.complicationset │ │ │ ├── Circular.imageset │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Extra Large.imageset │ │ │ │ └── Contents.json │ │ │ ├── Graphic Bezel.imageset │ │ │ │ └── Contents.json │ │ │ ├── Graphic Circular.imageset │ │ │ │ └── Contents.json │ │ │ ├── Graphic Corner.imageset │ │ │ │ └── Contents.json │ │ │ ├── Graphic Extra Large.imageset │ │ │ │ └── Contents.json │ │ │ ├── Graphic Large Rectangular.imageset │ │ │ │ └── Contents.json │ │ │ ├── Modular.imageset │ │ │ │ └── Contents.json │ │ │ └── Utilitarian.imageset │ │ │ │ └── Contents.json │ │ └── Contents.json │ ├── ExtensionDelegate.swift │ ├── Info.plist │ └── InterfaceController.swift └── MixpanelDemoWatch │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ ├── 100.png │ │ ├── 1024.png │ │ ├── 172.png │ │ ├── 196.png │ │ ├── 216.png │ │ ├── 48.png │ │ ├── 55.png │ │ ├── 58.png │ │ ├── 80.png │ │ ├── 87.png │ │ ├── 88.png │ │ └── Contents.json │ ├── Base.lproj │ └── Interface.storyboard │ └── Info.plist ├── Package.swift ├── README.md ├── Sources ├── AutomaticEvents.swift ├── AutomaticProperties.swift ├── Constants.swift ├── Data+Compression.swift ├── Error.swift ├── FeatureFlags.swift ├── FileLogging.swift ├── Flush.swift ├── FlushRequest.swift ├── Group.swift ├── JSONHandler.swift ├── MPDB.swift ├── Mixpanel.swift ├── Mixpanel │ └── PrivacyInfo.xcprivacy ├── MixpanelInstance.swift ├── MixpanelLogger.swift ├── MixpanelOptions.swift ├── MixpanelPersistence.swift ├── MixpanelType.swift ├── Network.swift ├── People.swift ├── PrintLogging.swift ├── ReadWriteLock.swift ├── SessionMetadata.swift └── Track.swift ├── docs ├── Classes.html ├── Classes │ ├── Group.html │ ├── Mixpanel.html │ ├── MixpanelInstance.html │ ├── MixpanelLogger.html │ ├── MixpanelOptions.html │ └── People.html ├── Enums.html ├── Enums │ ├── GzipError.html │ └── MixpanelLogLevel.html ├── Extensions.html ├── Extensions │ ├── Array.html │ ├── Bool.html │ ├── Data.html │ ├── Date.html │ ├── Dictionary.html │ ├── Double.html │ ├── Float.html │ ├── Int.html │ ├── NSArray.html │ ├── NSNull.html │ ├── NSNumber.html │ ├── NSString.html │ ├── Optional.html │ ├── String.html │ ├── UInt.html │ └── URL.html ├── Protocols.html ├── Protocols │ ├── MixpanelDelegate.html │ ├── MixpanelFlagDelegate.html │ ├── MixpanelFlags.html │ ├── MixpanelLogging.html │ ├── MixpanelProxyServerDelegate.html │ └── MixpanelType.html ├── Structs.html ├── Structs │ ├── MixpanelFlagVariant.html │ ├── MixpanelLogMessage.html │ ├── ProxyServerConfig.html │ └── ServerProxyResource.html ├── Typealiases.html ├── badge.svg ├── css │ ├── highlight.css │ └── jazzy.css ├── docsets │ ├── Mixpanel.docset │ │ └── Contents │ │ │ ├── Info.plist │ │ │ └── Resources │ │ │ ├── Documents │ │ │ ├── Classes.html │ │ │ ├── Classes │ │ │ │ ├── Group.html │ │ │ │ ├── Mixpanel.html │ │ │ │ ├── MixpanelInstance.html │ │ │ │ ├── MixpanelLogger.html │ │ │ │ ├── MixpanelOptions.html │ │ │ │ └── People.html │ │ │ ├── Enums.html │ │ │ ├── Enums │ │ │ │ ├── GzipError.html │ │ │ │ └── MixpanelLogLevel.html │ │ │ ├── Extensions.html │ │ │ ├── Extensions │ │ │ │ ├── Array.html │ │ │ │ ├── Bool.html │ │ │ │ ├── Data.html │ │ │ │ ├── Date.html │ │ │ │ ├── Dictionary.html │ │ │ │ ├── Double.html │ │ │ │ ├── Float.html │ │ │ │ ├── Int.html │ │ │ │ ├── NSArray.html │ │ │ │ ├── NSNull.html │ │ │ │ ├── NSNumber.html │ │ │ │ ├── NSString.html │ │ │ │ ├── Optional.html │ │ │ │ ├── String.html │ │ │ │ ├── UInt.html │ │ │ │ └── URL.html │ │ │ ├── Protocols.html │ │ │ ├── Protocols │ │ │ │ ├── MixpanelDelegate.html │ │ │ │ ├── MixpanelFlagDelegate.html │ │ │ │ ├── MixpanelFlags.html │ │ │ │ ├── MixpanelLogging.html │ │ │ │ ├── MixpanelProxyServerDelegate.html │ │ │ │ └── MixpanelType.html │ │ │ ├── Structs.html │ │ │ ├── Structs │ │ │ │ ├── MixpanelFlagVariant.html │ │ │ │ ├── MixpanelLogMessage.html │ │ │ │ ├── ProxyServerConfig.html │ │ │ │ └── ServerProxyResource.html │ │ │ ├── Typealiases.html │ │ │ ├── css │ │ │ │ ├── highlight.css │ │ │ │ └── jazzy.css │ │ │ ├── img │ │ │ │ ├── carat.png │ │ │ │ ├── dash.png │ │ │ │ ├── gh.png │ │ │ │ └── spinner.gif │ │ │ ├── index.html │ │ │ ├── js │ │ │ │ ├── jazzy.js │ │ │ │ ├── jazzy.search.js │ │ │ │ ├── jquery.min.js │ │ │ │ ├── lunr.min.js │ │ │ │ └── typeahead.jquery.js │ │ │ └── search.json │ │ │ └── docSet.dsidx │ └── Mixpanel.tgz ├── img │ ├── carat.png │ ├── dash.png │ ├── gh.png │ └── spinner.gif ├── index.html ├── js │ ├── jazzy.js │ ├── jazzy.search.js │ ├── jquery.min.js │ ├── lunr.min.js │ └── typeahead.jquery.js ├── search.json └── undocumented.json └── scripts ├── carthage.sh ├── carthage.xcconfig ├── generate_docs.sh └── release.py /.github/workflows/iOS.yml: -------------------------------------------------------------------------------- 1 | name: iOS CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: macos-latest 12 | strategy: 13 | matrix: 14 | destination: ['name=iPhone 15 Pro,OS=latest'] 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | 19 | # (Optional) Inspect what simulators are installed 20 | - name: Show simulators 21 | run: xcrun simctl list 22 | 23 | - name: Run Test 24 | working-directory: MixpanelDemo 25 | run: | 26 | set -o pipefail 27 | xcodebuild \ 28 | -scheme MixpanelDemo \ 29 | -derivedDataPath Build/ \ 30 | -destination "${{ matrix.destination }}" \ 31 | -configuration Debug \ 32 | ONLY_ACTIVE_ARCH=NO \ 33 | ENABLE_TESTABILITY=YES \ 34 | -enableCodeCoverage YES \ 35 | clean build test | xcpretty -c; 36 | 37 | - name: Pod Lint 38 | run: | 39 | gem install cocoapods 40 | pod lib lint --allow-warnings 41 | 42 | - name: Code Coverage Report 43 | working-directory: MixpanelDemo/build/Logs/Test 44 | run: | 45 | xcrun xccov view --report --files-for-target Mixpanel.framework *.xcresult 46 | xcrun xccov view --report --only-targets *.xcresult 47 | -------------------------------------------------------------------------------- /.github/workflows/macOS.yml: -------------------------------------------------------------------------------- 1 | name: macOS CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: macos-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Run Test 16 | working-directory: MixpanelDemo 17 | run: | 18 | set -o pipefail 19 | xcodebuild -scheme MixpanelDemoMac -derivedDataPath Build/ -configuration Debug ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES -enableCodeCoverage YES clean build test | xcpretty -c; 20 | - name: Code Coverage Report 21 | working-directory: MixpanelDemo/build/Logs/Test 22 | run: | 23 | xcrun xccov view --report --files-for-target Mixpanel.framework *.xcresult 24 | xcrun xccov view --report --only-targets *.xcresult 25 | -------------------------------------------------------------------------------- /.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 "jared" 38 | git config user.email "jared@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 | id: create_release 67 | uses: actions/create-release@v1 68 | env: 69 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 70 | with: 71 | tag_name: ${{ github.ref }} 72 | release_name: Release ${{ github.ref }} 73 | body: ${{ steps.generate-release-changelog.outputs.changelog }} 74 | 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | .DS_Store 3 | 4 | # Xcode 5 | build/* 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | !default.xcworkspace 15 | xcuserdata/ 16 | *.xcuserstate 17 | profile 18 | *.moved-aside 19 | *.cer 20 | *.p12 21 | *.mobileprovision 22 | 23 | # AppCode 24 | .idea 25 | 26 | # Swift Package Manager 27 | .swiftpm/ 28 | .build/ 29 | 30 | # Carthage 31 | 32 | Carthage/Build/ 33 | 34 | MixpanelDemo/build/ 35 | 36 | # Claude.ai instructions 37 | CLAUDE.md 38 | -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | swift: 2 | config_file: .swiftlint.yml 3 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | excluded: 2 | - .build/ 3 | - build/ 4 | - MixpanelDemo 5 | - Sources/SelectorEvaluator.swift 6 | - Sources/CodelessBinding.swift 7 | - Sources/TweakViewData.swift 8 | - Sources/WebSocketWrapper.swift 9 | - Sources/TakeoverNotificationViewController.swift 10 | - Sources/ObjectSelector.swift 11 | - Sources/SnapshotMessage.swift 12 | - Sources/InAppNotification.swift 13 | - Sources/DecideRequest.swift 14 | - Sources/UIControlBinding.swift 15 | - Sources/InAppNotifications.swift 16 | - Sources/WebSocket.swift 17 | - Sources/ObjectFilter.swift 18 | - Sources/SSLSecurity.swift 19 | - Sources/NSAttributedStringToNSDictionary.swift 20 | - Sources/MixpanelNotificationServiceExtension.swift 21 | - Sources/ObjectSerializer.swift 22 | - Sources/PushNotifications.swift 23 | - Sources/ObjectSerializerConfig.swift 24 | - Sources/TweakPersistency.swift 25 | - Sources/MD5.swift 26 | - Sources/BaseWebSocketMessage.swift 27 | - Sources/BaseNotificationViewController.swift 28 | - Sources/ObjectIdentityProvider.swift 29 | - Sources/UITableViewBinding.swift 30 | - Sources/UIViewSelectors.swift 31 | - Sources/MiniNotification.swift 32 | - Sources/VariantAction.swift 33 | - Sources/DisplayTrigger.swift 34 | - Sources/MiniNotificationViewController.swift 35 | - Sources/SHA256.swift 36 | - Sources/TakeoverNotification.swift 37 | - Sources/UIColorToNSString.swift 38 | - Sources/TweakStore.swift 39 | - Sources/Swizzle.swift 40 | - Sources/UIImageToNSDictionary.swift 41 | - Sources/Tweak.swift 42 | - Sources/DeviceInfoMessage.swift 43 | - Sources/ApplicationStateSerializer.swift 44 | 45 | line_length: 140 46 | file_length: 1500 47 | type_body_length: 500 48 | function_parameter_count: 7 49 | opt_in_rules: 50 | - empty_count 51 | - empty_string 52 | - unused_private_declaration 53 | disabled_rules: 54 | - cyclomatic_complexity 55 | - large_tuple 56 | - conditional_binding_cascade 57 | - force_cast 58 | - force_try 59 | - function_body_length 60 | - todo 61 | - type_body_length 62 | - variable_name 63 | - trailing_whitespace 64 | - unused_optional_binding 65 | -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 5.1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2017 Mixpanel. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Mixpanel-swift.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'Mixpanel-swift' 3 | s.version = '5.1.0' 4 | s.module_name = 'Mixpanel' 5 | s.license = 'Apache License, Version 2.0' 6 | s.summary = 'Mixpanel tracking library for iOS (Swift)' 7 | s.swift_version = '5.0' 8 | s.homepage = 'https://mixpanel.com' 9 | s.author = { 'Mixpanel, Inc' => 'support@mixpanel.com' } 10 | s.source = { :git => 'https://github.com/mixpanel/mixpanel-swift.git', 11 | :tag => "v#{s.version}" } 12 | s.resource_bundles = {'Mixpanel' => ['Sources/Mixpanel/PrivacyInfo.xcprivacy']} 13 | s.ios.deployment_target = '12.0' 14 | s.ios.frameworks = 'UIKit', 'Foundation', 'CoreTelephony' 15 | s.ios.pod_target_xcconfig = { 16 | 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => '$(inherited) IOS' 17 | } 18 | s.default_subspec = 'Complete' 19 | base_source_files = ['Sources/Network.swift', 'Sources/FlushRequest.swift', 'Sources/PrintLogging.swift', 'Sources/FileLogging.swift', 20 | 'Sources/MixpanelLogger.swift', 'Sources/JSONHandler.swift', 'Sources/Error.swift', 'Sources/AutomaticProperties.swift', 21 | 'Sources/Constants.swift', 'Sources/MixpanelType.swift', 'Sources/Mixpanel.swift', 'Sources/MixpanelInstance.swift', 22 | 'Sources/Flush.swift', 'Sources/Track.swift', 'Sources/People.swift', 'Sources/AutomaticEvents.swift', 23 | 'Sources/Group.swift', 'Sources/ReadWriteLock.swift', 'Sources/SessionMetadata.swift', 'Sources/MPDB.swift', 'Sources/MixpanelPersistence.swift', 24 | 'Sources/Data+Compression.swift', 'Sources/MixpanelOptions.swift', 'Sources/FeatureFlags.swift'] 25 | s.tvos.deployment_target = '11.0' 26 | s.tvos.frameworks = 'UIKit', 'Foundation' 27 | s.tvos.pod_target_xcconfig = { 28 | 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => '$(inherited) TV_OS' 29 | } 30 | s.osx.deployment_target = '10.13' 31 | s.osx.frameworks = 'Cocoa', 'Foundation' 32 | s.osx.pod_target_xcconfig = { 33 | 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => '$(inherited) MAC_OS' 34 | } 35 | 36 | s.watchos.deployment_target = '4.0' 37 | s.watchos.pod_target_xcconfig = { 38 | 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => '$(inherited) WATCH_OS' 39 | } 40 | 41 | s.subspec 'Complete' do |ss| 42 | ss.ios.source_files = ['Sources/*.swift'] 43 | ss.tvos.source_files = base_source_files 44 | ss.osx.source_files = base_source_files 45 | ss.watchos.source_files = base_source_files 46 | end 47 | 48 | s.subspec 'Core' do |ss| 49 | ss.ios.source_files = base_source_files 50 | ss.tvos.source_files = base_source_files 51 | ss.osx.source_files = base_source_files 52 | ss.watchos.source_files = base_source_files 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /Mixpanel.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Mixpanel.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Mixpanel.xcodeproj/xcshareddata/xcschemes/Mixpanel.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Mixpanel.xcodeproj/xcshareddata/xcschemes/Mixpanel_macOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Mixpanel.xcodeproj/xcshareddata/xcschemes/Mixpanel_tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Mixpanel.xcodeproj/xcshareddata/xcschemes/Mixpanel_watchOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /MixpanelDemo/.gitignore: -------------------------------------------------------------------------------- 1 | Podfile.lock 2 | Pods 3 | **/*.xccheckout 4 | **/*.moved-aside 5 | **/*.xcuserstate 6 | **/*.xcscheme 7 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo.xcodeproj/xcshareddata/xcschemes/MixpanelDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 32 | 33 | 39 | 40 | 41 | 42 | 45 | 51 | 52 | 53 | 55 | 56 | 58 | 59 | 61 | 62 | 63 | 64 | 65 | 66 | 78 | 80 | 86 | 87 | 88 | 89 | 93 | 94 | 95 | 96 | 102 | 104 | 110 | 111 | 112 | 113 | 115 | 116 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo.xcodeproj/xcshareddata/xcschemes/MixpanelDemoWatch (Notification).xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | 58 | 60 | 66 | 67 | 68 | 69 | 76 | 78 | 84 | 85 | 86 | 87 | 89 | 90 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo.xcodeproj/xcshareddata/xcschemes/MixpanelDemoWatch.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | 57 | 59 | 65 | 66 | 67 | 68 | 74 | 76 | 82 | 83 | 84 | 85 | 87 | 88 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/ActionCompleteViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActionCompleteViewController.swift 3 | // MixpanelDemo 4 | // 5 | // Created by Yarden Eitan on 7/18/16. 6 | // Copyright © 2016 Mixpanel. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ActionCompleteViewController: UIViewController { 12 | @IBOutlet weak var popupView: UIView! 13 | @IBOutlet weak var actionLabel: UILabel! 14 | @IBOutlet weak var descLabel: UILabel! 15 | var actionStr: String? 16 | var descStr: String? 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | popupView.clipsToBounds = true 22 | popupView.layer.cornerRadius = 6 23 | 24 | let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap)) 25 | view.addGestureRecognizer(tap) 26 | 27 | actionLabel.text = actionStr 28 | descLabel.text = descStr 29 | } 30 | 31 | override func viewDidAppear(_ animated: Bool) { 32 | super.viewDidAppear(animated) 33 | 34 | DispatchQueue.main.asyncAfter(deadline: .now() + 5) { 35 | self.dismiss(animated: true, completion: nil) 36 | } 37 | } 38 | 39 | @objc func handleTap(gesture: UITapGestureRecognizer) { 40 | dismiss(animated: true, completion: nil) 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MixpanelDemo 4 | // 5 | // Created by Yarden Eitan on 6/5/16. 6 | // Copyright © 2016 Mixpanel. All rights reserved. 7 | // 8 | 9 | import Mixpanel 10 | import UIKit 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | func application( 18 | _ application: UIApplication, 19 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil 20 | ) -> Bool { 21 | var ADD_YOUR_MIXPANEL_TOKEN_BELOW_🛠🛠🛠🛠🛠🛠: String 22 | let mixpanelOptions = MixpanelOptions(token: "MIXPANEL_TOKEN", trackAutomaticEvents: true) 23 | Mixpanel.initialize(options: mixpanelOptions) 24 | Mixpanel.mainInstance().loggingEnabled = true 25 | 26 | return true 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/AppIcon 1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemo/AppIcon 1024x1024.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "40.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "60.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "29.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "58.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "87.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "80.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "120.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "57x57", 47 | "idiom" : "iphone", 48 | "filename" : "57.png", 49 | "scale" : "1x" 50 | }, 51 | { 52 | "size" : "57x57", 53 | "idiom" : "iphone", 54 | "filename" : "114.png", 55 | "scale" : "2x" 56 | }, 57 | { 58 | "size" : "60x60", 59 | "idiom" : "iphone", 60 | "filename" : "120.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "60x60", 65 | "idiom" : "iphone", 66 | "filename" : "180.png", 67 | "scale" : "3x" 68 | }, 69 | { 70 | "size" : "20x20", 71 | "idiom" : "ipad", 72 | "filename" : "20.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "20x20", 77 | "idiom" : "ipad", 78 | "filename" : "40.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "29x29", 83 | "idiom" : "ipad", 84 | "filename" : "29.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "29x29", 89 | "idiom" : "ipad", 90 | "filename" : "58.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "40x40", 95 | "idiom" : "ipad", 96 | "filename" : "40.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "40x40", 101 | "idiom" : "ipad", 102 | "filename" : "80.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "50x50", 107 | "idiom" : "ipad", 108 | "filename" : "50.png", 109 | "scale" : "1x" 110 | }, 111 | { 112 | "size" : "50x50", 113 | "idiom" : "ipad", 114 | "filename" : "100.png", 115 | "scale" : "2x" 116 | }, 117 | { 118 | "size" : "72x72", 119 | "idiom" : "ipad", 120 | "filename" : "72.png", 121 | "scale" : "1x" 122 | }, 123 | { 124 | "size" : "72x72", 125 | "idiom" : "ipad", 126 | "filename" : "144.png", 127 | "scale" : "2x" 128 | }, 129 | { 130 | "size" : "76x76", 131 | "idiom" : "ipad", 132 | "filename" : "76.png", 133 | "scale" : "1x" 134 | }, 135 | { 136 | "size" : "76x76", 137 | "idiom" : "ipad", 138 | "filename" : "152.png", 139 | "scale" : "2x" 140 | }, 141 | { 142 | "size" : "83.5x83.5", 143 | "idiom" : "ipad", 144 | "filename" : "167.png", 145 | "scale" : "2x" 146 | }, 147 | { 148 | "size" : "1024x1024", 149 | "idiom" : "ios-marketing", 150 | "filename" : "1024.png", 151 | "scale" : "1x" 152 | } 153 | ], 154 | "info" : { 155 | "version" : 1, 156 | "author" : "xcode" 157 | } 158 | } -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/GDPRViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GDPRViewController.swift 3 | // MixpanelDemo 4 | // 5 | // Created by Zihe Jia on 4/5/18. 6 | // Copyright © 2018 Mixpanel. All rights reserved. 7 | // 8 | 9 | import Mixpanel 10 | import UIKit 11 | 12 | class GDPRViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { 13 | 14 | @IBOutlet weak var tableView: UITableView! 15 | var tableViewItems = [ 16 | "Opt Out", 17 | "Check Opted Out Flag", 18 | "Opt In", 19 | "Opt In w DistinctId", 20 | "Opt In w DistinctId & Properties", 21 | "Init with default opt-out", 22 | "Init with default opt-in", 23 | ] 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | tableView.delegate = self 28 | tableView.dataSource = self 29 | } 30 | 31 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 32 | let cell = self.tableView.dequeueReusableCell(withIdentifier: "cell")! as UITableViewCell 33 | cell.textLabel?.text = tableViewItems[indexPath.item] 34 | cell.textLabel?.textColor = #colorLiteral( 35 | red: 0.200000003, green: 0.200000003, blue: 0.200000003, alpha: 1) 36 | return cell 37 | } 38 | 39 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 40 | tableView.deselectRow(at: indexPath, animated: true) 41 | 42 | let actionStr = tableViewItems[indexPath.item] 43 | var descStr = "" 44 | 45 | switch indexPath.item { 46 | case 0: 47 | Mixpanel.mainInstance().optOutTracking() 48 | descStr = "Opted out" 49 | case 1: 50 | descStr = "Opt-out flag is \(Mixpanel.mainInstance().hasOptedOutTracking())" 51 | case 2: 52 | Mixpanel.mainInstance().optInTracking() 53 | descStr = "Opted In" 54 | case 3: 55 | Mixpanel.mainInstance().optInTracking(distinctId: "aDistinctIdForOptIn") 56 | descStr = "Opt In with distinctId 'aDistinctIdForOptIn'" 57 | case 4: 58 | let p: Properties = [ 59 | "a": 1, 60 | "b": 2.3, 61 | "c": ["4", 5] as [Any], 62 | "d": URL(string: "https://mixpanel.com")!, 63 | "e": NSNull(), 64 | "f": Date(), 65 | ] 66 | Mixpanel.mainInstance().optInTracking(distinctId: "aDistinctIdForOptIn", properties: p) 67 | descStr = "Opt In with distinctId 'aDistinctIdForOptIn' and \(p)" 68 | case 5: 69 | Mixpanel.initialize( 70 | token: "testtoken", trackAutomaticEvents: true, optOutTrackingByDefault: true) 71 | descStr = 72 | "Init Mixpanel with default opt-out(sample only), to make it work, place it in your startup stage of your app" 73 | case 6: 74 | Mixpanel.initialize( 75 | token: "testtoken", trackAutomaticEvents: true, optOutTrackingByDefault: false) 76 | descStr = 77 | "Init Mixpanel with default opt-in(sample only), to make it work, place it in your startup stage of your app" 78 | default: 79 | break 80 | } 81 | 82 | let vc = 83 | storyboard!.instantiateViewController(withIdentifier: "ActionCompleteViewController") 84 | as! ActionCompleteViewController 85 | vc.actionStr = actionStr 86 | vc.descStr = descStr 87 | vc.modalTransitionStyle = UIModalTransitionStyle.crossDissolve 88 | vc.modalPresentationStyle = UIModalPresentationStyle.overFullScreen 89 | present(vc, animated: true, completion: nil) 90 | } 91 | 92 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 93 | return tableViewItems.count 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/GroupsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GroupsViewController.swift 3 | // MixpanelDemo 4 | // 5 | // Created by Iris McLeary on 9/7/18. 6 | // Copyright © 2018 Mixpanel. All rights reserved. 7 | // 8 | 9 | import Mixpanel 10 | import UIKit 11 | 12 | class GroupsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { 13 | 14 | @IBOutlet weak var tableView: UITableView! 15 | var tableViewItems = [ 16 | "Set Properties", 17 | "Set One Property", 18 | "Set Properties Once", 19 | "Unset Property", 20 | "Remove Property", 21 | "Union Properties", 22 | "Delete Group", 23 | "Set Group", 24 | "Set One Group", 25 | "Add Group", 26 | "Remove Group", 27 | "Track with Groups", 28 | ] 29 | 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | tableView.delegate = self 33 | tableView.dataSource = self 34 | } 35 | 36 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 37 | let cell = self.tableView.dequeueReusableCell(withIdentifier: "cell")! as UITableViewCell 38 | cell.textLabel?.text = tableViewItems[indexPath.item] 39 | cell.textLabel?.textColor = #colorLiteral( 40 | red: 0.200000003, green: 0.200000003, blue: 0.200000003, alpha: 1) 41 | return cell 42 | } 43 | 44 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 45 | tableView.deselectRow(at: indexPath, animated: true) 46 | 47 | let actionStr = tableViewItems[indexPath.item] 48 | var descStr = "" 49 | 50 | let groupKey = "Cool Property" 51 | let groupID = 12345 52 | 53 | switch indexPath.item { 54 | case 0: 55 | let p: Properties = [ 56 | "a": 1, 57 | "b": 2.3, 58 | "c": ["4", 5] as [Any], 59 | "d": URL(string: "https://mixpanel.com")!, 60 | "e": NSNull(), 61 | "f": Date(), 62 | ] 63 | Mixpanel.mainInstance().getGroup(groupKey: groupKey, groupID: groupID).set(properties: p) 64 | descStr = "Properties: \(p)" 65 | case 1: 66 | Mixpanel.mainInstance().getGroup(groupKey: groupKey, groupID: groupID).set( 67 | property: "g", to: "yo") 68 | descStr = "Property key: g, value: yo" 69 | case 2: 70 | let p = ["h": "just once"] 71 | Mixpanel.mainInstance().getGroup(groupKey: groupKey, groupID: groupID).setOnce(properties: p) 72 | descStr = "Properties: \(p)" 73 | case 3: 74 | let p = "b" 75 | Mixpanel.mainInstance().getGroup(groupKey: groupKey, groupID: groupID).unset(property: p) 76 | descStr = "Unset Property: \(p)" 77 | case 4: 78 | Mixpanel.mainInstance().getGroup(groupKey: groupKey, groupID: groupID).remove( 79 | key: "c", value: 5) 80 | descStr = "Remove Property: [\"c\" : 5]" 81 | case 5: 82 | let p = ["c": [5, 4]] 83 | Mixpanel.mainInstance().getGroup(groupKey: groupKey, groupID: groupID).union( 84 | key: "c", values: p["c"]!) 85 | descStr = "Properties: \(p)" 86 | case 6: 87 | Mixpanel.mainInstance().getGroup(groupKey: groupKey, groupID: groupID).deleteGroup() 88 | descStr = "Deleted Group" 89 | case 7: 90 | let groupIDs = [groupID, 301] 91 | Mixpanel.mainInstance().setGroup(groupKey: groupKey, groupIDs: groupIDs) 92 | descStr = "Set Group \(groupKey) to \(groupIDs)" 93 | case 8: 94 | Mixpanel.mainInstance().setGroup(groupKey: groupKey, groupID: groupID) 95 | descStr = "Set Group \(groupKey) to \(groupID)" 96 | case 9: 97 | let newID = "iris_test3" 98 | Mixpanel.mainInstance().addGroup(groupKey: groupKey, groupID: newID) 99 | descStr = "Add Group \(groupKey), ID \(newID)" 100 | case 10: 101 | Mixpanel.mainInstance().removeGroup(groupKey: groupKey, groupID: groupID) 102 | descStr = "Remove Group \(groupKey), ID \(groupID)" 103 | case 11: 104 | let p: Properties = [ 105 | "a": 1, 106 | "b": 2.3, 107 | "c": ["4", 5] as [Any], 108 | "d": URL(string: "https://mixpanel.com")!, 109 | "e": NSNull(), 110 | "f": Date(), 111 | "Cool Property": "foo", 112 | ] 113 | let groups: Properties = ["Cool Property": "actual group value"] 114 | Mixpanel.mainInstance().trackWithGroups( 115 | event: "tracked with groups", properties: p, groups: groups) 116 | descStr = "Track with groups: properties \(p), groups \(groups)" 117 | default: 118 | break 119 | } 120 | 121 | let vc = 122 | storyboard!.instantiateViewController(withIdentifier: "ActionCompleteViewController") 123 | as! ActionCompleteViewController 124 | vc.actionStr = actionStr 125 | vc.descStr = descStr 126 | vc.modalTransitionStyle = UIModalTransitionStyle.crossDissolve 127 | vc.modalPresentationStyle = UIModalPresentationStyle.overFullScreen 128 | present(vc, animated: true, completion: nil) 129 | 130 | } 131 | 132 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 133 | return tableViewItems.count 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | UIFileSharingEnabled 31 | 32 | UILaunchStoryboardName 33 | LaunchScreen 34 | UIMainStoryboardFile 35 | Main 36 | UIRequiredDeviceCapabilities 37 | 38 | armv7 39 | 40 | UISupportedInterfaceOrientations 41 | 42 | UIInterfaceOrientationLandscapeLeft 43 | UIInterfaceOrientationLandscapeRight 44 | UIInterfaceOrientationPortrait 45 | 46 | UISupportedInterfaceOrientations~ipad 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationPortraitUpsideDown 50 | UIInterfaceOrientationLandscapeLeft 51 | UIInterfaceOrientationLandscapeRight 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/LoginViewController.swift: -------------------------------------------------------------------------------- 1 | import Mixpanel 2 | import UIKit 3 | 4 | class LoginViewController: UIViewController { 5 | 6 | let delegate = UIApplication.shared.delegate as! AppDelegate 7 | 8 | @IBOutlet weak var projectTokenTextField: UITextField! 9 | 10 | @IBOutlet weak var distinctIdTextField: UITextField! 11 | 12 | @IBOutlet weak var nameTextField: UITextField! 13 | 14 | @IBOutlet weak var startButton: UIButton! 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | let token = Mixpanel.mainInstance().apiToken 19 | projectTokenTextField.text = token 20 | 21 | distinctIdTextField.text = "demo_user" 22 | nameTextField.text = "Demo User" 23 | } 24 | 25 | open func goToMainView() { 26 | if let vc = storyboard?.instantiateViewController(withIdentifier: "mainNav") { 27 | self.view.window?.rootViewController = vc 28 | } else { 29 | NSLog("Unable to find view controller with name \"mainView\"") 30 | } 31 | } 32 | 33 | @IBAction func start(_ sender: Any) { 34 | Mixpanel.mainInstance().identify(distinctId: distinctIdTextField.text ?? "demo_user") 35 | Mixpanel.mainInstance().people.set(property: "$name", to: nameTextField.text ?? "") 36 | Mixpanel.mainInstance().track(event: "Logged in") 37 | Mixpanel.mainInstance().flush() 38 | 39 | goToMainView() 40 | } 41 | 42 | @IBAction func rateDevX(_ sender: Any) { 43 | if let url = URL(string: "https://www.mixpanel.com/devnps") { 44 | UIApplication.shared.open(url) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/MixpanelDemo.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/PeopleViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PeopleViewController.swift 3 | // MixpanelDemo 4 | // 5 | // Created by Yarden Eitan on 7/15/16. 6 | // Copyright © 2016 Mixpanel. All rights reserved. 7 | // 8 | 9 | import Mixpanel 10 | import UIKit 11 | 12 | class PeopleViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { 13 | 14 | @IBOutlet weak var tableView: UITableView! 15 | var tableViewItems = [ 16 | "Set Properties", 17 | "Set One Property", 18 | "Set Properties Once", 19 | "Unset Properties", 20 | "Incremet Properties", 21 | "Increment Property", 22 | "Append Properties", 23 | "Union Properties", 24 | "Track Charge w/o Properties", 25 | "Track Charge w Properties", 26 | "Clear Charges", 27 | "Delete User", 28 | "Identify", 29 | ] 30 | 31 | override func viewDidLoad() { 32 | super.viewDidLoad() 33 | tableView.delegate = self 34 | tableView.dataSource = self 35 | } 36 | 37 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 38 | let cell = self.tableView.dequeueReusableCell(withIdentifier: "cell")! as UITableViewCell 39 | cell.textLabel?.text = tableViewItems[indexPath.item] 40 | cell.textLabel?.textColor = #colorLiteral( 41 | red: 0.200000003, green: 0.200000003, blue: 0.200000003, alpha: 1) 42 | return cell 43 | } 44 | 45 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 46 | tableView.deselectRow(at: indexPath, animated: true) 47 | 48 | let actionStr = tableViewItems[indexPath.item] 49 | var descStr = "" 50 | 51 | switch indexPath.item { 52 | case 0: 53 | let p: Properties = [ 54 | "a": 1, 55 | "b": 2.3, 56 | "c": ["4", 5] as [Any], 57 | "d": URL(string: "https://mixpanel.com")!, 58 | "e": NSNull(), 59 | "f": Date(), 60 | ] 61 | Mixpanel.mainInstance().people.set(properties: p) 62 | descStr = "Properties: \(p)" 63 | case 1: 64 | Mixpanel.mainInstance().people.set(property: "g", to: "yo") 65 | descStr = "Property key: g, value: yo" 66 | case 2: 67 | let p = ["h": "just once"] 68 | Mixpanel.mainInstance().people.setOnce(properties: p) 69 | descStr = "Properties: \(p)" 70 | case 3: 71 | let p = ["b", "h"] 72 | Mixpanel.mainInstance().people.unset(properties: p) 73 | descStr = "Unset Properties: \(p)" 74 | case 4: 75 | let p = ["a": 1.2, "b": 3] 76 | Mixpanel.mainInstance().people.increment(properties: p) 77 | descStr = "Properties: \(p)" 78 | case 5: 79 | Mixpanel.mainInstance().people.increment(property: "b", by: 2.3) 80 | descStr = "Property key: b, value increment: 2.3" 81 | case 6: 82 | let p = ["c": "hello", "d": "goodbye"] 83 | Mixpanel.mainInstance().people.append(properties: p) 84 | descStr = "Properties: \(p)" 85 | case 7: 86 | let p = ["c": ["goodbye", "hi"], "d": ["hello"]] 87 | Mixpanel.mainInstance().people.union(properties: p) 88 | descStr = "Properties: \(p)" 89 | case 8: 90 | Mixpanel.mainInstance().people.trackCharge(amount: 20.5) 91 | descStr = "Amount: 20.5" 92 | case 9: 93 | let p = ["sandwich": 1] 94 | Mixpanel.mainInstance().people.trackCharge(amount: 12.8, properties: p) 95 | descStr = "Amount: 12.8, Properties: \(p)" 96 | case 10: 97 | Mixpanel.mainInstance().people.clearCharges() 98 | descStr = "Cleared Charges" 99 | case 11: 100 | Mixpanel.mainInstance().people.deleteUser() 101 | descStr = "Deleted User" 102 | case 12: 103 | // Mixpanel People requires that you explicitly set a distinct ID for the current user. In this case, 104 | // we're using the automatically generated distinct ID from event tracking, based on the device's MAC address. 105 | // It is strongly recommended that you use the same distinct IDs for Mixpanel Engagement and Mixpanel People. 106 | // Note that the call to Mixpanel People identify: can come after properties have been set. We queue them until 107 | // identify: is called and flush them at that time. That way, you can set properties before a user is logged in 108 | // and identify them once you know their user ID. 109 | Mixpanel.mainInstance().identify(distinctId: "testDistinctId111") 110 | descStr = "Identified User" 111 | default: 112 | break 113 | } 114 | 115 | let vc = 116 | storyboard!.instantiateViewController(withIdentifier: "ActionCompleteViewController") 117 | as! ActionCompleteViewController 118 | vc.actionStr = actionStr 119 | vc.descStr = descStr 120 | vc.modalTransitionStyle = UIModalTransitionStyle.crossDissolve 121 | vc.modalPresentationStyle = UIModalPresentationStyle.overFullScreen 122 | present(vc, animated: true, completion: nil) 123 | 124 | } 125 | 126 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 127 | return tableViewItems.count 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/TrackingViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackingViewController.swift 3 | // MixpanelDemo 4 | // 5 | // Created by Yarden Eitan on 7/15/16. 6 | // Copyright © 2016 Mixpanel. All rights reserved. 7 | // 8 | 9 | import Mixpanel 10 | import UIKit 11 | 12 | class TrackingViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { 13 | 14 | @IBOutlet weak var tableView: UITableView! 15 | var tableViewItems = [ 16 | "Track w/o Properties", 17 | "Track w Properties", 18 | "Time Event 5secs", 19 | "Clear Timed Events", 20 | "Get Current SuperProperties", 21 | "Clear SuperProperties", 22 | "Register SuperProperties", 23 | "Register SuperProperties Once", 24 | "Register SP Once w Default Value", 25 | "Unregister SuperProperty", 26 | ] 27 | 28 | override func viewDidLoad() { 29 | super.viewDidLoad() 30 | 31 | tableView.delegate = self 32 | tableView.dataSource = self 33 | } 34 | 35 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 36 | let cell = self.tableView.dequeueReusableCell(withIdentifier: "cell")! as UITableViewCell 37 | cell.textLabel?.text = tableViewItems[indexPath.item] 38 | cell.textLabel?.textColor = #colorLiteral( 39 | red: 0.200000003, green: 0.200000003, blue: 0.200000003, alpha: 1) 40 | return cell 41 | } 42 | 43 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 44 | tableView.deselectRow(at: indexPath, animated: true) 45 | 46 | let actionStr = self.tableViewItems[indexPath.item] 47 | var descStr = "" 48 | 49 | switch indexPath.item { 50 | case 0: 51 | let ev = "Track Event!" 52 | Mixpanel.mainInstance().track(event: ev) 53 | descStr = "Event: \"\(ev)\"" 54 | case 1: 55 | let ev = "Track Event With Properties!" 56 | let p = ["Cool Property": "Property Value"] 57 | Mixpanel.mainInstance().track(event: ev, properties: p) 58 | descStr = "Event: \"\(ev)\"\n Properties: \(p)" 59 | case 2: 60 | let ev = "Timed Event" 61 | Mixpanel.mainInstance().time(event: ev) 62 | DispatchQueue.main.asyncAfter(deadline: .now() + 5) { 63 | Mixpanel.mainInstance().track(event: ev) 64 | } 65 | descStr = "Timed Event: \"\(ev)\"" 66 | case 3: 67 | Mixpanel.mainInstance().clearTimedEvents() 68 | descStr = "Timed Events Cleared" 69 | case 4: 70 | descStr = "Super Properties:\n" 71 | descStr += "\(Mixpanel.mainInstance().currentSuperProperties())" 72 | case 5: 73 | Mixpanel.mainInstance().clearSuperProperties() 74 | descStr = "Cleared Super Properties" 75 | case 6: 76 | let p: Properties = [ 77 | "Super Property 1": 1, 78 | "Super Property 2": "p2", 79 | "Super Property 3": Date(), 80 | "Super Property 4": ["a": "b"], 81 | "Super Property 5": [3, "a", Date()] as [Any], 82 | "Super Property 6": 83 | URL(string: "https://mixpanel.com")!, 84 | "Super Property 7": NSNull(), 85 | ] 86 | Mixpanel.mainInstance().registerSuperProperties(p) 87 | descStr = "Properties: \(p)" 88 | case 7: 89 | let p = ["Super Property 1": 2.3] 90 | Mixpanel.mainInstance().registerSuperPropertiesOnce(p) 91 | descStr = "Properties: \(p)" 92 | case 8: 93 | let p = ["Super Property 1": 1.2] 94 | Mixpanel.mainInstance().registerSuperPropertiesOnce(p, defaultValue: 2.3) 95 | descStr = "Properties: \(p) with Default Value: 2.3" 96 | case 9: 97 | let p = "Super Property 2" 98 | Mixpanel.mainInstance().unregisterSuperProperty(p) 99 | descStr = "Properties: \(p)" 100 | default: 101 | break 102 | } 103 | 104 | let vc = 105 | storyboard!.instantiateViewController(withIdentifier: "ActionCompleteViewController") 106 | as! ActionCompleteViewController 107 | vc.actionStr = actionStr 108 | vc.descStr = descStr 109 | vc.modalTransitionStyle = UIModalTransitionStyle.crossDissolve 110 | vc.modalPresentationStyle = UIModalPresentationStyle.overFullScreen 111 | present(vc, animated: true, completion: nil) 112 | } 113 | 114 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 115 | return tableViewItems.count 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemo/UtilityViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UtilityViewController.swift 3 | // MixpanelDemo 4 | // 5 | // Created by Yarden Eitan on 7/15/16. 6 | // Copyright © 2016 Mixpanel. All rights reserved. 7 | // 8 | 9 | import Mixpanel 10 | import StoreKit 11 | import UIKit 12 | 13 | class UtilityViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, 14 | SKProductsRequestDelegate, SKPaymentTransactionObserver 15 | { 16 | 17 | @IBOutlet weak var tableView: UITableView! 18 | var tableViewItems = [ 19 | "Create Alias", 20 | "Reset", 21 | "Archive", 22 | "Flush", 23 | "In-App Purchase", 24 | ] 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | tableView.delegate = self 29 | tableView.dataSource = self 30 | } 31 | 32 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 33 | let cell = self.tableView.dequeueReusableCell(withIdentifier: "cell")! as UITableViewCell 34 | cell.textLabel?.text = tableViewItems[indexPath.item] 35 | cell.textLabel?.textColor = #colorLiteral( 36 | red: 0.200000003, green: 0.200000003, blue: 0.200000003, alpha: 1) 37 | return cell 38 | } 39 | 40 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 41 | tableView.deselectRow(at: indexPath, animated: true) 42 | 43 | let actionStr = tableViewItems[indexPath.item] 44 | var descStr = "" 45 | 46 | switch indexPath.item { 47 | case 0: 48 | Mixpanel.mainInstance().createAlias( 49 | "New Alias", distinctId: Mixpanel.mainInstance().distinctId) 50 | descStr = "Alias: New Alias, from: \(Mixpanel.mainInstance().distinctId)" 51 | case 1: 52 | Mixpanel.mainInstance().reset() 53 | descStr = "Reset Instance" 54 | case 2: 55 | Mixpanel.mainInstance().archive() 56 | descStr = "Archived Data" 57 | case 3: 58 | Mixpanel.mainInstance().flush() 59 | descStr = "Flushed Data" 60 | case 4: 61 | IAPFlow() 62 | default: 63 | break 64 | } 65 | 66 | let vc = 67 | storyboard!.instantiateViewController(withIdentifier: "ActionCompleteViewController") 68 | as! ActionCompleteViewController 69 | vc.actionStr = actionStr 70 | vc.descStr = descStr 71 | vc.modalTransitionStyle = UIModalTransitionStyle.crossDissolve 72 | vc.modalPresentationStyle = UIModalPresentationStyle.overFullScreen 73 | present(vc, animated: true, completion: nil) 74 | } 75 | 76 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 77 | return tableViewItems.count 78 | } 79 | 80 | func IAPFlow() { 81 | let productIdentifiers = NSSet( 82 | objects: 83 | "com.iaptutorial.fun", 84 | "com.mixpanel.swiftsdkdemo.fun" 85 | ) 86 | let productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers as! Set) 87 | productsRequest.delegate = self 88 | productsRequest.start() 89 | } 90 | 91 | func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { 92 | if response.products.count > 0 { 93 | if let firstProduct = response.products.first { 94 | let payment = SKPayment(product: firstProduct) 95 | SKPaymentQueue.default().add(self) 96 | SKPaymentQueue.default().add(payment) 97 | } 98 | } 99 | } 100 | 101 | func paymentQueue( 102 | _ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction] 103 | ) { 104 | for transaction: AnyObject in transactions { 105 | if let trans = transaction as? SKPaymentTransaction { 106 | switch trans.transactionState { 107 | case .purchased: 108 | SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction) 109 | print("IAP purchased") 110 | break 111 | 112 | case .failed: 113 | SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction) 114 | print("IAP failed") 115 | break 116 | case .restored: 117 | SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction) 118 | print("IAP restored") 119 | break 120 | 121 | default: break 122 | } 123 | } 124 | } 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoMac/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MixpanelDemoMac 4 | // 5 | // Created by ZIHE JIA on 6/7/21. 6 | // Copyright © 2021 Mixpanel. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Mixpanel 11 | 12 | @main 13 | class AppDelegate: NSObject, NSApplicationDelegate { 14 | 15 | func applicationDidFinishLaunching(_ aNotification: Notification) { 16 | // Insert code here to initialize your application 17 | 18 | var ADD_YOUR_MIXPANEL_TOKEN_BELOW_🛠🛠🛠🛠🛠🛠: String 19 | 20 | Mixpanel.initialize(token: "MIXPANEL_TOKEN") 21 | Mixpanel.mainInstance().loggingEnabled = true 22 | Mixpanel.mainInstance().track(event: "Tracked Event") 23 | 24 | } 25 | 26 | func applicationWillTerminate(_ aNotification: Notification) { 27 | // Insert code here to tear down your application 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoMac/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoMac/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoMac/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoMac/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2021 Mixpanel. All rights reserved. 27 | NSMainStoryboardFile 28 | Main 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoMac/MixpanelDemoMac.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | com.apple.security.network.client 10 | 11 | com.apple.security.network.server 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoMac/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // MixpanelDemoMac 4 | // 5 | // Created by ZIHE JIA on 6/7/21. 6 | // Copyright © 2021 Mixpanel. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Mixpanel 11 | 12 | class ViewController: NSViewController { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | 17 | // Do any additional setup after loading the view. 18 | } 19 | 20 | override var representedObject: Any? { 21 | didSet { 22 | // Update the view, if already loaded. 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoMacTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoMacTests/MixpanelBaseTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MixpanelBaseTests.swift 3 | // MixpanelDemo 4 | // 5 | // Created by Yarden Eitan on 6/29/16. 6 | // Copyright © 2016 Mixpanel. All rights reserved. 7 | // 8 | 9 | import SQLite3 10 | import XCTest 11 | 12 | @testable import Mixpanel 13 | @testable import MixpanelDemoMac 14 | 15 | class MixpanelBaseTests: XCTestCase, MixpanelDelegate { 16 | var mixpanelWillFlush: Bool! 17 | static var requestCount = 0 18 | 19 | override func setUp() { 20 | NSLog("starting test setup...") 21 | super.setUp() 22 | mixpanelWillFlush = false 23 | 24 | NSLog("finished test setup") 25 | } 26 | 27 | override func tearDown() { 28 | super.tearDown() 29 | } 30 | 31 | func removeDBfile(apiToken: String) { 32 | do { 33 | let fileManager = FileManager.default 34 | 35 | // Check if file exists 36 | if fileManager.fileExists(atPath: dbFilePath(apiToken)) { 37 | // Delete file 38 | try fileManager.removeItem(atPath: dbFilePath(apiToken)) 39 | } else { 40 | print( 41 | "Unable to delete the test db file at \(dbFilePath(apiToken)), the file does not exist") 42 | } 43 | } catch let error as NSError { 44 | print("An error took place: \(error)") 45 | } 46 | } 47 | 48 | func removeDBfile(_ mixpanel: MixpanelInstance) { 49 | mixpanel.mixpanelPersistence.closeDB() 50 | removeDBfile(apiToken: mixpanel.apiToken) 51 | } 52 | 53 | func dbFilePath(_ token: String? = nil) -> String { 54 | let manager = FileManager.default 55 | #if os(iOS) 56 | let url = manager.urls(for: .libraryDirectory, in: .userDomainMask).last 57 | #else 58 | let url = manager.urls(for: .cachesDirectory, in: .userDomainMask).last 59 | #endif // os(iOS) 60 | guard let apiToken = token else { 61 | return "" 62 | } 63 | 64 | guard let urlUnwrapped = url?.appendingPathComponent("\(token ?? apiToken)_MPDB.sqlite").path 65 | else { 66 | return "" 67 | } 68 | return urlUnwrapped 69 | } 70 | 71 | func mixpanelWillFlush(_ mixpanel: MixpanelInstance) -> Bool { 72 | return mixpanelWillFlush 73 | } 74 | 75 | func waitForTrackingQueue(_ mixpanel: MixpanelInstance) { 76 | mixpanel.trackingQueue.sync { 77 | mixpanel.networkQueue.sync { 78 | return 79 | } 80 | } 81 | 82 | mixpanel.trackingQueue.sync { 83 | mixpanel.networkQueue.sync { 84 | return 85 | } 86 | } 87 | } 88 | 89 | func randomId() -> String { 90 | return String(format: "%08x%08x", arc4random(), arc4random()) 91 | } 92 | 93 | func waitForAsyncTasks() { 94 | var hasCompletedTask = false 95 | DispatchQueue.main.async { 96 | hasCompletedTask = true 97 | } 98 | 99 | let loopUntil = Date(timeIntervalSinceNow: 10) 100 | while !hasCompletedTask && loopUntil.timeIntervalSinceNow > 0 { 101 | RunLoop.current.run(mode: RunLoop.Mode.default, before: loopUntil) 102 | } 103 | } 104 | 105 | func eventQueue(token: String) -> Queue { 106 | return MixpanelPersistence.init(token: token).loadEntitiesInBatch(type: .events) 107 | } 108 | 109 | func peopleQueue(token: String) -> Queue { 110 | return MixpanelPersistence.init(token: token).loadEntitiesInBatch(type: .people) 111 | } 112 | 113 | func unIdentifiedPeopleQueue(token: String) -> Queue { 114 | return MixpanelPersistence.init(token: token).loadEntitiesInBatch( 115 | type: .people, flag: PersistenceConstant.unIdentifiedFlag) 116 | } 117 | 118 | func groupQueue(token: String) -> Queue { 119 | return MixpanelPersistence.init(token: token).loadEntitiesInBatch(type: .groups) 120 | } 121 | 122 | func flushAndWaitForTrackingQueue(_ mixpanel: MixpanelInstance) { 123 | mixpanel.flush() 124 | waitForTrackingQueue(mixpanel) 125 | mixpanel.flush() 126 | waitForTrackingQueue(mixpanel) 127 | } 128 | 129 | func assertDefaultPeopleProperties(_ properties: InternalProperties) { 130 | XCTAssertNotNil(properties["$ios_device_model"], "missing $ios_device_model property") 131 | XCTAssertNotNil(properties["$ios_lib_version"], "missing $ios_lib_version property") 132 | XCTAssertNotNil(properties["$ios_version"], "missing $ios_version property") 133 | XCTAssertNotNil(properties["$ios_app_version"], "missing $ios_app_version property") 134 | XCTAssertNotNil(properties["$ios_app_release"], "missing $ios_app_release property") 135 | } 136 | 137 | func compareDate(dateString: String, dateDate: Date) { 138 | let dateFormatter: ISO8601DateFormatter = ISO8601DateFormatter() 139 | let date = dateFormatter.string(from: dateDate) 140 | XCTAssertEqual(String(date.prefix(19)), String(dateString.prefix(19))) 141 | } 142 | 143 | func allPropertyTypes() -> Properties { 144 | let dateFormatter = DateFormatter() 145 | dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss zzz" 146 | let date = dateFormatter.date(from: "2012-09-28 19:14:36 PDT") 147 | let nested = ["p1": ["p2": ["p3": ["bottom"]]]] 148 | let opt: String? = nil 149 | return [ 150 | "string": "yello", 151 | "number": 3, 152 | "date": date!, 153 | "dictionary": ["k": "v", "opt": opt as Any], 154 | "array": ["1", opt as Any], 155 | "null": NSNull(), 156 | "nested": nested, 157 | "url": URL(string: "https://mixpanel.com/")!, 158 | "float": 1.3, 159 | "optional": opt, 160 | ] 161 | } 162 | 163 | } 164 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoMacTests/MixpanelDemoMacTests-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoMacTests/TestConstants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestConstants.swift 3 | // MixpanelDemo 4 | // 5 | // Created by Yarden Eitan on 6/28/16. 6 | // Copyright © 2016 Mixpanel. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import Mixpanel 12 | 13 | let kFakeServerUrl = "https://34a272abf23d.com" 14 | 15 | extension XCTestCase { 16 | 17 | func XCTExpectAssert( 18 | _ expectedMessage: String, file: StaticString = #file, line: UInt = #line, block: () -> Void 19 | ) { 20 | let exp = expectation(description: expectedMessage) 21 | 22 | Assertions.assertClosure = { 23 | (condition, message, file, line) in 24 | if !condition { 25 | exp.fulfill() 26 | } 27 | } 28 | 29 | // Call code. 30 | block() 31 | waitForExpectations(timeout: 0.5, handler: nil) 32 | Assertions.assertClosure = Assertions.swiftAssertClosure 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoMacUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoMacUITests/MixpanelDemoMacUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MixpanelDemoMacUITests.swift 3 | // MixpanelDemoMacUITests 4 | // 5 | // Created by ZIHE JIA on 6/7/21. 6 | // Copyright © 2021 Mixpanel. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class MixpanelDemoMacUITests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | 16 | // In UI tests it is usually best to stop immediately when a failure occurs. 17 | continueAfterFailure = false 18 | 19 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 20 | } 21 | 22 | override func tearDownWithError() throws { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | } 25 | 26 | func testExample() throws { 27 | // UI tests must launch the application that they test. 28 | let app = XCUIApplication() 29 | app.launch() 30 | 31 | // Use recording to get started writing UI tests. 32 | // Use XCTAssert and related functions to verify your tests produce the correct results. 33 | } 34 | 35 | func testLaunchPerformance() throws { 36 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { 37 | // This measures how long it takes to launch your application. 38 | measure(metrics: [XCTApplicationLaunchMetric()]) { 39 | XCUIApplication().launch() 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTV/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MixpanelDemoTV 4 | // 5 | // Created by Zihe Jia on 3/22/19. 6 | // Copyright © 2019 Mixpanel. All rights reserved. 7 | // 8 | 9 | import Mixpanel 10 | import UIKit 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | func application( 18 | _ application: UIApplication, 19 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 20 | ) -> Bool { 21 | // Override point for customization after application launch. 22 | print("didFinishLaunchingWithOptions") 23 | 24 | var ADD_YOUR_MIXPANEL_TOKEN_BELOW_🛠🛠🛠🛠🛠🛠: String 25 | Mixpanel.initialize(token: "MIXPANEL_TOKEN") 26 | Mixpanel.mainInstance().loggingEnabled = true 27 | Mixpanel.mainInstance().registerSuperProperties(["super apple tv properties": 1]) 28 | Mixpanel.mainInstance().track(event: "apple tv track") 29 | 30 | return true 31 | } 32 | 33 | func applicationWillResignActive(_ application: UIApplication) { 34 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 35 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 36 | print("applicationWillResignActive") 37 | } 38 | 39 | func applicationDidEnterBackground(_ application: UIApplication) { 40 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 41 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 42 | print("applicationDidEnterBackground") 43 | } 44 | 45 | func applicationWillEnterForeground(_ application: UIApplication) { 46 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 47 | print("applicationWillEnterForeground") 48 | } 49 | 50 | func applicationDidBecomeActive(_ application: UIApplication) { 51 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 52 | print("applicationDidBecomeActive") 53 | } 54 | 55 | func applicationWillTerminate(_ application: UIApplication) { 56 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 57 | print("applicationWillTerminate") 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv" 5 | } 6 | ], 7 | "info" : { 8 | "version" : 1, 9 | "author" : "xcode" 10 | } 11 | } -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv" 5 | } 6 | ], 7 | "info" : { 8 | "version" : 1, 9 | "author" : "xcode" 10 | } 11 | } -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv" 5 | } 6 | ], 7 | "info" : { 8 | "version" : 1, 9 | "author" : "xcode" 10 | } 11 | } -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "version" : 1, 14 | "author" : "xcode" 15 | } 16 | } -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "version" : 1, 14 | "author" : "xcode" 15 | } 16 | } -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | } 11 | ], 12 | "info" : { 13 | "version" : 1, 14 | "author" : "xcode" 15 | } 16 | } -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "size" : "1280x768", 5 | "idiom" : "tv", 6 | "filename" : "App Icon - App Store.imagestack", 7 | "role" : "primary-app-icon" 8 | }, 9 | { 10 | "size" : "400x240", 11 | "idiom" : "tv", 12 | "filename" : "App Icon.imagestack", 13 | "role" : "primary-app-icon" 14 | }, 15 | { 16 | "size" : "2320x720", 17 | "idiom" : "tv", 18 | "filename" : "Top Shelf Image Wide.imageset", 19 | "role" : "top-shelf-image-wide" 20 | }, 21 | { 22 | "size" : "1920x720", 23 | "idiom" : "tv", 24 | "filename" : "Top Shelf Image.imageset", 25 | "role" : "top-shelf-image" 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | } 32 | } -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "tv-marketing", 13 | "scale" : "1x" 14 | }, 15 | { 16 | "idiom" : "tv-marketing", 17 | "scale" : "2x" 18 | } 19 | ], 20 | "info" : { 21 | "version" : 1, 22 | "author" : "xcode" 23 | } 24 | } -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "tv", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "tv-marketing", 13 | "scale" : "1x" 14 | }, 15 | { 16 | "idiom" : "tv-marketing", 17 | "scale" : "2x" 18 | } 19 | ], 20 | "info" : { 21 | "version" : 1, 22 | "author" : "xcode" 23 | } 24 | } -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTV/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTV/Assets.xcassets/Launch Image.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "landscape", 5 | "idiom" : "tv", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "11.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "landscape", 12 | "idiom" : "tv", 13 | "extent" : "full-screen", 14 | "minimum-system-version" : "9.0", 15 | "scale" : "1x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTV/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | LSRequiresIPhoneOS 22 | 23 | UIMainStoryboardFile 24 | Main 25 | UIRequiredDeviceCapabilities 26 | 27 | arm64 28 | 29 | UIUserInterfaceStyle 30 | Automatic 31 | 32 | 33 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTV/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // MixpanelDemoTV 4 | // 5 | // Created by Zihe Jia on 3/22/19. 6 | // Copyright © 2019 Mixpanel. All rights reserved. 7 | // 8 | 9 | import Mixpanel 10 | import UIKit 11 | 12 | class ViewController: UIViewController { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | // Do any additional setup after loading the view, typically from a nib. 17 | } 18 | 19 | @IBAction func timeEventClicked(_ sender: Any) { 20 | Mixpanel.mainInstance().time(event: "time something") 21 | DispatchQueue.main.asyncAfter(deadline: .now() + 5) { 22 | Mixpanel.mainInstance().track(event: "time something") 23 | } 24 | } 25 | 26 | @IBAction func TrackEventClicked(_ sender: Any) { 27 | Mixpanel.mainInstance().track( 28 | event: "Player Create", properties: ["gender": "Male", "weapon": "Pistol"]) 29 | } 30 | 31 | @IBAction func peopleClicked(_ sender: Any) { 32 | let mixpanel = Mixpanel.mainInstance() 33 | mixpanel.people.set(properties: ["gender": "Male", "weapon": "Pistol"]) 34 | mixpanel.identify(distinctId: mixpanel.distinctId) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTVTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTVTests/MixpanelDemoTVTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MixpanelDemoTVTests.swift 3 | // MixpanelDemoTVTests 4 | // 5 | // Created by Zihe Jia on 3/22/19. 6 | // Copyright © 2019 Mixpanel. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import MixpanelDemoTV 12 | 13 | class MixpanelDemoTVTests: XCTestCase { 14 | 15 | override func setUp() { 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | } 22 | 23 | func testExample() { 24 | // This is an example of a functional test case. 25 | // Use XCTAssert and related functions to verify your tests produce the correct results. 26 | } 27 | 28 | func testPerformanceExample() { 29 | // This is an example of a performance test case. 30 | self.measure { 31 | // Put the code you want to measure the time of here. 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTVUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTVUITests/MixpanelDemoTVUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MixpanelDemoTVUITests.swift 3 | // MixpanelDemoTVUITests 4 | // 5 | // Created by Zihe Jia on 3/22/19. 6 | // Copyright © 2019 Mixpanel. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class MixpanelDemoTVUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | 16 | // In UI tests it is usually best to stop immediately when a failure occurs. 17 | continueAfterFailure = false 18 | 19 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 20 | XCUIApplication().launch() 21 | 22 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 23 | } 24 | 25 | override func tearDown() { 26 | // Put teardown code here. This method is called after the invocation of each test method in the class. 27 | } 28 | 29 | func testExample() { 30 | // Use recording to get started writing UI tests. 31 | // Use XCTAssert and related functions to verify your tests produce the correct results. 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTests/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTests/Assets.xcassets/checkerboard.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "checkerboard.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTests/Assets.xcassets/checkerboard.imageset/checkerboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemoTests/Assets.xcassets/checkerboard.imageset/checkerboard.jpg -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | NSAppTransportSecurity 24 | 25 | NSAllowsArbitraryLoads 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTests/JSONHandlerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONHandlerTests.swift 3 | // MixpanelDemoTests 4 | // 5 | // Created by Jared McFarland on 5/28/21. 6 | // Copyright © 2021 Mixpanel. All rights reserved. 7 | // 8 | import XCTest 9 | 10 | @testable import Mixpanel 11 | @testable import MixpanelDemo 12 | 13 | class JSONHandlerTests: XCTestCase { 14 | 15 | func testSerializeJSONObject() { 16 | let nSNumberProp: NSNumber = NSNumber(value: 1) 17 | let doubleProp: Double = 2.0 18 | let floatProp: Float = Float(3.5) 19 | let stringProp: String = "string" 20 | let intProp: Int = -4 21 | let uIntProp: UInt = 4 22 | let uInt64Prop: UInt64 = 5_000_000_000 23 | let boolProp: Bool = true 24 | let optArrayProp: [Double?] = [nil, 1.0, 2.0] 25 | let arrayProp: [Double] = [0.0, 1.0, 2.0] 26 | let dictProp: [String: String?] = ["nil": nil, "a": "a", "b": "b"] 27 | let dateProp: Date = Date() 28 | let urlProp: URL = URL(string: "https://www.mixpanel.com")! 29 | let nilProp: String? = nil 30 | let nestedDictProp: [String: [String: String?]] = ["nested": dictProp] 31 | let nestedArraryProp: [[Double?]] = [optArrayProp] 32 | 33 | let event: [String: Any] = [ 34 | "event": "test", 35 | "properties": [ 36 | "nSNumberProp": nSNumberProp, 37 | "doubleProp": doubleProp, 38 | "floatProp": floatProp, 39 | "stringProp": stringProp, 40 | "intProp": intProp, 41 | "uIntProp": uIntProp, 42 | "uInt64Prop": uInt64Prop, 43 | "boolProp": boolProp, 44 | "optArrayProp": optArrayProp, 45 | "arrayProp": arrayProp, 46 | "dictProp": dictProp, 47 | "dateProp": dateProp, 48 | "urlProp": urlProp, 49 | "nilProp": nilProp as Any, 50 | "nestedDictProp": nestedDictProp, 51 | "nestedArraryProp": nestedArraryProp, 52 | ], 53 | ] 54 | 55 | let serializedQueue = JSONHandler.serializeJSONObject([event]) 56 | let deserializedQueue = 57 | try! JSONSerialization.jsonObject(with: serializedQueue!, options: []) as! [[String: Any]] 58 | XCTAssertEqual(deserializedQueue[0]["event"] as! String, "test") 59 | let props = deserializedQueue[0]["properties"] as! [String: Any] 60 | XCTAssertEqual(props["nSNumberProp"] as! NSNumber, nSNumberProp) 61 | XCTAssertEqual(props["doubleProp"] as! Double, doubleProp) 62 | XCTAssertEqual(props["floatProp"] as! Float, floatProp) 63 | XCTAssertEqual(props["stringProp"] as! String, stringProp) 64 | XCTAssertEqual(props["intProp"] as! Int, intProp) 65 | XCTAssertEqual(props["uIntProp"] as! UInt, uIntProp) 66 | XCTAssertEqual(props["uInt64Prop"] as! UInt64, uInt64Prop) 67 | XCTAssertEqual(props["boolProp"] as! Bool, boolProp) 68 | // nil should be dropped from Array properties 69 | XCTAssertEqual(props["optArrayProp"] as! Array, [1.0, 2.0]) 70 | XCTAssertEqual(props["arrayProp"] as! Array, arrayProp) 71 | let deserializedDictProp = props["dictProp"] as! [String: Any] 72 | // nil should be convereted to NSNull() inside Dictionary properties 73 | XCTAssertEqual(deserializedDictProp["nil"] as! NSNull, NSNull()) 74 | XCTAssertEqual(deserializedDictProp["a"] as! String, "a") 75 | XCTAssertEqual(deserializedDictProp["b"] as! String, "b") 76 | XCTAssertEqual(props["urlProp"] as! String, urlProp.absoluteString) 77 | // nil properties themselves should also be converted to NSNull() 78 | XCTAssertEqual(props["nilProp"] as! NSNull, NSNull()) 79 | let deserializedNestedDictProp = props["nestedDictProp"] as! [String: [String: Any]] 80 | let nestedDict = deserializedNestedDictProp["nested"]! 81 | // the same nil logic from above should be applied to nested Collections as well 82 | XCTAssertEqual(nestedDict["nil"] as! NSNull, NSNull()) 83 | XCTAssertEqual(nestedDict["a"] as! String, "a") 84 | XCTAssertEqual(nestedDict["b"] as! String, "b") 85 | let deserializednestedArraryProp = props["nestedArraryProp"] as! [[Double?]] 86 | XCTAssertEqual(deserializednestedArraryProp[0] as! Array, [1.0, 2.0]) 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTests/LoggerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MixpanelLoggerTests.swift 3 | // MixpanelDemo 4 | // 5 | // Created by Sam Green on 7/8/16. 6 | // Copyright © 2016 Mixpanel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | 12 | @testable import Mixpanel 13 | 14 | class MixpanelLoggerTests: XCTestCase { 15 | 16 | func testEnableDebug() { 17 | let counter = CounterLogging() 18 | MixpanelLogger.addLogging(counter) 19 | MixpanelLogger.enableLevel(.debug) 20 | 21 | MixpanelLogger.debug(message: "logged") 22 | XCTAssertEqual(1, counter.count) 23 | } 24 | 25 | func testEnableInfo() { 26 | let counter = CounterLogging() 27 | MixpanelLogger.addLogging(counter) 28 | MixpanelLogger.enableLevel(.info) 29 | MixpanelLogger.info(message: "logged") 30 | XCTAssertEqual(1, counter.count) 31 | } 32 | 33 | func testEnableWarning() { 34 | let counter = CounterLogging() 35 | MixpanelLogger.addLogging(counter) 36 | MixpanelLogger.enableLevel(.warning) 37 | MixpanelLogger.warn(message: "logged") 38 | XCTAssertEqual(1, counter.count) 39 | } 40 | 41 | func testEnableError() { 42 | let counter = CounterLogging() 43 | MixpanelLogger.addLogging(counter) 44 | MixpanelLogger.enableLevel(.error) 45 | MixpanelLogger.error(message: "logged") 46 | XCTAssertEqual(1, counter.count) 47 | } 48 | 49 | func testDisabledLogging() { 50 | let counter = CounterLogging() 51 | MixpanelLogger.addLogging(counter) 52 | MixpanelLogger.disableLevel(.debug) 53 | MixpanelLogger.debug(message: "not logged") 54 | XCTAssertEqual(0, counter.count) 55 | 56 | MixpanelLogger.disableLevel(.error) 57 | MixpanelLogger.error(message: "not logged") 58 | XCTAssertEqual(0, counter.count) 59 | 60 | MixpanelLogger.disableLevel(.info) 61 | MixpanelLogger.info(message: "not logged") 62 | XCTAssertEqual(0, counter.count) 63 | 64 | MixpanelLogger.disableLevel(.warning) 65 | MixpanelLogger.warn(message: "not logged") 66 | XCTAssertEqual(0, counter.count) 67 | } 68 | } 69 | 70 | /// This is a stub that implements `MixpanelLogging` to be passed to our `MixpanelLogger` instance for testing 71 | class CounterLogging: MixpanelLogging { 72 | var count = 0 73 | 74 | func addMessage(message: MixpanelLogMessage) { 75 | count = count + 1 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTests/MixpanelBaseTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MixpanelBaseTests.swift 3 | // MixpanelDemo 4 | // 5 | // Created by Yarden Eitan on 6/29/16. 6 | // Copyright © 2016 Mixpanel. All rights reserved. 7 | // 8 | 9 | import SQLite3 10 | import XCTest 11 | 12 | @testable import Mixpanel 13 | 14 | class MixpanelBaseTests: XCTestCase, MixpanelDelegate { 15 | var mixpanelWillFlush: Bool! 16 | static var requestCount = 0 17 | 18 | override func setUp() { 19 | NSLog("starting test setup...") 20 | super.setUp() 21 | mixpanelWillFlush = false 22 | let defaults = UserDefaults(suiteName: "Mixpanel") 23 | defaults?.removeObject(forKey: "MPFirstOpen") 24 | 25 | NSLog("finished test setup") 26 | } 27 | 28 | override func tearDown() { 29 | super.tearDown() 30 | } 31 | 32 | func removeDBfile(_ token: String? = nil) { 33 | do { 34 | let fileManager = FileManager.default 35 | 36 | // Check if file exists 37 | if fileManager.fileExists(atPath: dbFilePath(token)) { 38 | // Delete file 39 | try fileManager.removeItem(atPath: dbFilePath(token)) 40 | } else { 41 | print("Unable to delete the test db file at \(dbFilePath(token)), the file does not exist") 42 | } 43 | 44 | } catch let error as NSError { 45 | print("An error took place: \(error)") 46 | } 47 | } 48 | 49 | func dbFilePath(_ token: String? = nil) -> String { 50 | let manager = FileManager.default 51 | #if os(iOS) 52 | let url = manager.urls(for: .libraryDirectory, in: .userDomainMask).last 53 | #else 54 | let url = manager.urls(for: .cachesDirectory, in: .userDomainMask).last 55 | #endif // os(iOS) 56 | guard let apiToken = token else { 57 | return "" 58 | } 59 | 60 | guard let urlUnwrapped = url?.appendingPathComponent("\(token ?? apiToken)_MPDB.sqlite").path 61 | else { 62 | return "" 63 | } 64 | return urlUnwrapped 65 | } 66 | 67 | func mixpanelWillFlush(_ mixpanel: MixpanelInstance) -> Bool { 68 | return mixpanelWillFlush 69 | } 70 | 71 | func waitForTrackingQueue(_ mixpanel: MixpanelInstance) { 72 | mixpanel.trackingQueue.sync { 73 | mixpanel.networkQueue.sync { 74 | return 75 | } 76 | } 77 | mixpanel.trackingQueue.sync { 78 | mixpanel.networkQueue.sync { 79 | return 80 | } 81 | } 82 | } 83 | 84 | func randomId() -> String { 85 | return String(format: "%08x%08x", arc4random(), arc4random()) 86 | } 87 | 88 | func waitForAsyncTasks() { 89 | var hasCompletedTask = false 90 | DispatchQueue.main.async { 91 | hasCompletedTask = true 92 | } 93 | 94 | let loopUntil = Date(timeIntervalSinceNow: 10) 95 | while !hasCompletedTask && loopUntil.timeIntervalSinceNow > 0 { 96 | RunLoop.current.run(mode: RunLoop.Mode.default, before: loopUntil) 97 | } 98 | } 99 | 100 | func eventQueue(token: String) -> Queue { 101 | return MixpanelPersistence.init(instanceName: token).loadEntitiesInBatch(type: .events) 102 | } 103 | 104 | func peopleQueue(token: String) -> Queue { 105 | return MixpanelPersistence.init(instanceName: token).loadEntitiesInBatch(type: .people) 106 | } 107 | 108 | func unIdentifiedPeopleQueue(token: String) -> Queue { 109 | return MixpanelPersistence.init(instanceName: token).loadEntitiesInBatch( 110 | type: .people, flag: PersistenceConstant.unIdentifiedFlag) 111 | } 112 | 113 | func groupQueue(token: String) -> Queue { 114 | return MixpanelPersistence.init(instanceName: token).loadEntitiesInBatch(type: .groups) 115 | } 116 | 117 | func flushAndWaitForTrackingQueue(_ mixpanel: MixpanelInstance) { 118 | mixpanel.flush() 119 | waitForTrackingQueue(mixpanel) 120 | mixpanel.flush() 121 | waitForTrackingQueue(mixpanel) 122 | } 123 | 124 | func assertDefaultPeopleProperties(_ properties: InternalProperties) { 125 | XCTAssertNotNil(properties["$ios_device_model"], "missing $ios_device_model property") 126 | XCTAssertNotNil(properties["$ios_lib_version"], "missing $ios_lib_version property") 127 | XCTAssertNotNil(properties["$ios_version"], "missing $ios_version property") 128 | XCTAssertNotNil(properties["$ios_app_version"], "missing $ios_app_version property") 129 | XCTAssertNotNil(properties["$ios_app_release"], "missing $ios_app_release property") 130 | } 131 | 132 | func compareDate(dateString: String, dateDate: Date) { 133 | let dateFormatter: ISO8601DateFormatter = ISO8601DateFormatter() 134 | let date = dateFormatter.string(from: dateDate) 135 | XCTAssertEqual(String(date.prefix(19)), String(dateString.prefix(19))) 136 | } 137 | 138 | func allPropertyTypes() -> Properties { 139 | let dateFormatter = DateFormatter() 140 | dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss zzz" 141 | let date = dateFormatter.date(from: "2012-09-28 19:14:36 PDT") 142 | let nested = ["p1": ["p2": ["p3": ["bottom"]]]] 143 | let opt: String? = nil 144 | return [ 145 | "string": "yello", 146 | "number": 3, 147 | "date": date!, 148 | "dictionary": ["k": "v", "opt": opt as Any], 149 | "array": ["1", opt as Any], 150 | "null": NSNull(), 151 | "nested": nested, 152 | "url": URL(string: "https://mixpanel.com/")!, 153 | "float": 1.3, 154 | "optional": opt, 155 | ] 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTests/TestConstants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestConstants.swift 3 | // MixpanelDemo 4 | // 5 | // Created by Yarden Eitan on 6/28/16. 6 | // Copyright © 2016 Mixpanel. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | @testable import Mixpanel 12 | 13 | let kFakeServerUrl = "https://34a272abf23d.com" 14 | 15 | extension XCTestCase { 16 | 17 | func XCTExpectAssert( 18 | _ expectedMessage: String, file: StaticString = #file, line: UInt = #line, block: () -> Void 19 | ) { 20 | let exp = expectation(description: expectedMessage) 21 | 22 | Assertions.assertClosure = { 23 | (condition, message, file, line) in 24 | if !condition { 25 | exp.fulfill() 26 | } 27 | } 28 | 29 | // Call code. 30 | block() 31 | waitForExpectations(timeout: 60, handler: nil) 32 | Assertions.assertClosure = Assertions.swiftAssertClosure 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTests/mixpanel-testToken-events: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemoTests/mixpanel-testToken-events -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTests/mixpanel-testToken-groups: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemoTests/mixpanel-testToken-groups -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTests/mixpanel-testToken-optOutStatus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemoTests/mixpanel-testToken-optOutStatus -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTests/mixpanel-testToken-people: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemoTests/mixpanel-testToken-people -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoTests/mixpanel-testToken-properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemoTests/mixpanel-testToken-properties -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch Extension/Assets.xcassets/Complication.complicationset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "filename" : "Circular.imageset", 5 | "idiom" : "watch", 6 | "role" : "circular" 7 | }, 8 | { 9 | "filename" : "Extra Large.imageset", 10 | "idiom" : "watch", 11 | "role" : "extra-large" 12 | }, 13 | { 14 | "filename" : "Graphic Bezel.imageset", 15 | "idiom" : "watch", 16 | "role" : "graphic-bezel" 17 | }, 18 | { 19 | "filename" : "Graphic Circular.imageset", 20 | "idiom" : "watch", 21 | "role" : "graphic-circular" 22 | }, 23 | { 24 | "filename" : "Graphic Corner.imageset", 25 | "idiom" : "watch", 26 | "role" : "graphic-corner" 27 | }, 28 | { 29 | "filename" : "Graphic Extra Large.imageset", 30 | "idiom" : "watch", 31 | "role" : "graphic-extra-large" 32 | }, 33 | { 34 | "filename" : "Graphic Large Rectangular.imageset", 35 | "idiom" : "watch", 36 | "role" : "graphic-large-rectangular" 37 | }, 38 | { 39 | "filename" : "Modular.imageset", 40 | "idiom" : "watch", 41 | "role" : "modular" 42 | }, 43 | { 44 | "filename" : "Utilitarian.imageset", 45 | "idiom" : "watch", 46 | "role" : "utilitarian" 47 | } 48 | ], 49 | "info" : { 50 | "author" : "xcode", 51 | "version" : 1 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Extra Large.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch Extension/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch Extension/ExtensionDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExtensionDelegate.swift 3 | // MixpanelDemoWatch Extension 4 | // 5 | // Created by Zihe Jia on 3/21/19. 6 | // Copyright © 2019 Mixpanel. All rights reserved. 7 | // 8 | 9 | import Mixpanel 10 | import WatchKit 11 | 12 | class ExtensionDelegate: NSObject, WKExtensionDelegate { 13 | 14 | func applicationDidFinishLaunching() { 15 | var ADD_YOUR_MIXPANEL_TOKEN_BELOW_🛠🛠🛠🛠🛠🛠: String 16 | Mixpanel.initialize(token: "MIXPANEL_TOKEN") 17 | Mixpanel.mainInstance().loggingEnabled = true 18 | Mixpanel.mainInstance().registerSuperProperties(["super watch properties": 1]) 19 | // Perform any final initialization of your application. 20 | } 21 | 22 | func applicationDidBecomeActive() { 23 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 24 | } 25 | 26 | func applicationWillResignActive() { 27 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 28 | // Use this method to pause ongoing tasks, disable timers, etc. 29 | } 30 | 31 | func handle(_ backgroundTasks: Set) { 32 | // Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one. 33 | for task in backgroundTasks { 34 | // Use a switch statement to check the task type 35 | switch task { 36 | case let backgroundTask as WKApplicationRefreshBackgroundTask: 37 | // Be sure to complete the background task once you’re done. 38 | backgroundTask.setTaskCompletedWithSnapshot(false) 39 | case let snapshotTask as WKSnapshotRefreshBackgroundTask: 40 | // Snapshot tasks have a unique completion call, make sure to set your expiration date 41 | snapshotTask.setTaskCompleted( 42 | restoredDefaultState: true, estimatedSnapshotExpiration: Date.distantFuture, userInfo: nil 43 | ) 44 | case let connectivityTask as WKWatchConnectivityRefreshBackgroundTask: 45 | // Be sure to complete the connectivity task once you’re done. 46 | connectivityTask.setTaskCompletedWithSnapshot(false) 47 | case let urlSessionTask as WKURLSessionRefreshBackgroundTask: 48 | // Be sure to complete the URL session task once you’re done. 49 | urlSessionTask.setTaskCompletedWithSnapshot(false) 50 | case let relevantShortcutTask as WKRelevantShortcutRefreshBackgroundTask: 51 | // Be sure to complete the relevant-shortcut task once you're done. 52 | relevantShortcutTask.setTaskCompletedWithSnapshot(false) 53 | case let intentDidRunTask as WKIntentDidRunRefreshBackgroundTask: 54 | // Be sure to complete the intent-did-run task once you're done. 55 | intentDidRunTask.setTaskCompletedWithSnapshot(false) 56 | default: 57 | // make sure to complete unhandled task types 58 | task.setTaskCompletedWithSnapshot(false) 59 | } 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch Extension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | MixpanelDemoWatch Extension 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSExtension 24 | 25 | NSExtensionAttributes 26 | 27 | WKAppBundleIdentifier 28 | com.mixpanel.swiftsdkdemo.watchkitapp 29 | 30 | NSExtensionPointIdentifier 31 | com.apple.watchkit 32 | 33 | WKExtensionDelegateClassName 34 | $(PRODUCT_MODULE_NAME).ExtensionDelegate 35 | 36 | 37 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch Extension/InterfaceController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InterfaceController.swift 3 | // MixpanelDemoWatch Extension 4 | // 5 | // Created by Zihe Jia on 3/21/19. 6 | // Copyright © 2019 Mixpanel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Mixpanel 11 | import WatchKit 12 | 13 | class InterfaceController: WKInterfaceController { 14 | 15 | @IBOutlet weak var timeSomethingButton: WKInterfaceButton! 16 | 17 | var currentlyTiming = false 18 | 19 | override func awake(withContext context: Any?) { 20 | super.awake(withContext: context) 21 | } 22 | 23 | override func willActivate() { 24 | // This method is called when watch view controller is about to be visible to user 25 | super.willActivate() 26 | } 27 | 28 | @IBAction func trackButtonTapped() { 29 | Mixpanel.mainInstance().track(event: "trackButtonTapped") 30 | } 31 | 32 | @IBAction func timeButtonTapped() { 33 | if !currentlyTiming { 34 | Mixpanel.mainInstance().time(event: "time something") 35 | timeSomethingButton.setTitle("Finish Timing") 36 | } else { 37 | Mixpanel.mainInstance().track(event: "time something") 38 | timeSomethingButton.setTitle("Time Something") 39 | } 40 | currentlyTiming = !currentlyTiming 41 | } 42 | 43 | @IBAction func identifyButtonTapped() { 44 | let watchName = WKInterfaceDevice.current().systemName 45 | Mixpanel.mainInstance().people.set(properties: ["watch": watchName]) 46 | Mixpanel.mainInstance().identify(distinctId: Mixpanel.mainInstance().distinctId) 47 | } 48 | 49 | override func didDeactivate() { 50 | // This method is called when watch view controller is no longer visible 51 | super.didDeactivate() 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemoWatch/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemoWatch/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch/Assets.xcassets/AppIcon.appiconset/172.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemoWatch/Assets.xcassets/AppIcon.appiconset/172.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch/Assets.xcassets/AppIcon.appiconset/196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemoWatch/Assets.xcassets/AppIcon.appiconset/196.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch/Assets.xcassets/AppIcon.appiconset/216.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemoWatch/Assets.xcassets/AppIcon.appiconset/216.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch/Assets.xcassets/AppIcon.appiconset/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemoWatch/Assets.xcassets/AppIcon.appiconset/48.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch/Assets.xcassets/AppIcon.appiconset/55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemoWatch/Assets.xcassets/AppIcon.appiconset/55.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemoWatch/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemoWatch/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemoWatch/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch/Assets.xcassets/AppIcon.appiconset/88.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/MixpanelDemo/MixpanelDemoWatch/Assets.xcassets/AppIcon.appiconset/88.png -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | {"images":[{"idiom":"watch","filename":"172.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"86x86","expected-size":"172","role":"quickLook"},{"idiom":"watch","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"40x40","expected-size":"80","role":"appLauncher"},{"idiom":"watch","filename":"88.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"40mm","scale":"2x","size":"44x44","expected-size":"88","role":"appLauncher"},{"idiom":"watch","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"44mm","scale":"2x","size":"50x50","expected-size":"100","role":"appLauncher"},{"idiom":"watch","filename":"196.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"42mm","scale":"2x","size":"98x98","expected-size":"196","role":"quickLook"},{"idiom":"watch","filename":"216.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"44mm","scale":"2x","size":"108x108","expected-size":"216","role":"quickLook"},{"idiom":"watch","filename":"48.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"24x24","expected-size":"48","role":"notificationCenter"},{"idiom":"watch","filename":"55.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"42mm","scale":"2x","size":"27.5x27.5","expected-size":"55","role":"notificationCenter"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch","role":"companionSettings","scale":"3x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch","role":"companionSettings","scale":"2x"},{"size":"1024x1024","expected-size":"1024","filename":"1024.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch-marketing","scale":"1x"}]} -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch/Base.lproj/Interface.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /MixpanelDemo/MixpanelDemoWatch/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | MixpanelDemo 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | UISupportedInterfaceOrientations 24 | 25 | UIInterfaceOrientationPortrait 26 | UIInterfaceOrientationPortraitUpsideDown 27 | 28 | WKCompanionAppBundleIdentifier 29 | com.mixpanel.swiftsdkdemo 30 | WKWatchKitApp 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "Mixpanel", 7 | platforms: [ 8 | .iOS(.v12), 9 | .tvOS(.v11), 10 | .macOS(.v10_13), 11 | .watchOS(.v4), 12 | ], 13 | products: [ 14 | .library(name: "Mixpanel", targets: ["Mixpanel"]) 15 | ], 16 | targets: [ 17 | .target( 18 | name: "Mixpanel", 19 | path: "Sources", 20 | resources: [ 21 | .copy("Mixpanel/PrivacyInfo.xcprivacy") 22 | ] 23 | ) 24 | ] 25 | ) 26 | -------------------------------------------------------------------------------- /Sources/AutomaticProperties.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AutomaticProperties.swift 3 | // Mixpanel 4 | // 5 | // Created by Yarden Eitan on 7/8/16. 6 | // Copyright © 2016 Mixpanel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if os(iOS) || os(tvOS) || os(visionOS) 12 | import UIKit 13 | #elseif os(macOS) 14 | import Cocoa 15 | #elseif canImport(WatchKit) 16 | import WatchKit 17 | #endif 18 | 19 | class AutomaticProperties { 20 | static let automaticPropertiesLock = ReadWriteLock(label: "automaticPropertiesLock") 21 | 22 | static var properties: InternalProperties = { 23 | var p = InternalProperties() 24 | 25 | #if os(iOS) || os(tvOS) 26 | var screenSize: CGSize? = nil 27 | screenSize = UIScreen.main.bounds.size 28 | if let screenSize = screenSize { 29 | p["$screen_height"] = Int(screenSize.height) 30 | p["$screen_width"] = Int(screenSize.width) 31 | } 32 | #if targetEnvironment(macCatalyst) 33 | p["$os"] = "macOS" 34 | p["$os_version"] = ProcessInfo.processInfo.operatingSystemVersionString 35 | #else 36 | if AutomaticProperties.isiOSAppOnMac() { 37 | // iOS App Running on Apple Silicon Mac 38 | p["$os"] = "macOS" 39 | // unfortunately, there is no API that reports the correct macOS version 40 | // for "Designed for iPad" apps running on macOS, so we omit it here rather than mis-report 41 | } else { 42 | p["$os"] = UIDevice.current.systemName 43 | p["$os_version"] = UIDevice.current.systemVersion 44 | } 45 | #endif 46 | #elseif os(macOS) 47 | if let screenSize = NSScreen.main?.frame.size { 48 | p["$screen_height"] = Int(screenSize.height) 49 | p["$screen_width"] = Int(screenSize.width) 50 | } 51 | p["$os"] = "macOS" 52 | p["$os_version"] = ProcessInfo.processInfo.operatingSystemVersionString 53 | #elseif os(watchOS) 54 | let watchDevice = WKInterfaceDevice.current() 55 | p["$os"] = watchDevice.systemName 56 | p["$os_version"] = watchDevice.systemVersion 57 | p["$watch_model"] = AutomaticProperties.watchModel() 58 | let screenSize = watchDevice.screenBounds.size 59 | p["$screen_width"] = Int(screenSize.width) 60 | p["$screen_height"] = Int(screenSize.height) 61 | #elseif os(visionOS) 62 | p["$os"] = "visionOS" 63 | p["$os_version"] = UIDevice.current.systemVersion 64 | #endif 65 | 66 | let infoDict = Bundle.main.infoDictionary ?? [:] 67 | p["$app_build_number"] = infoDict["CFBundleVersion"] as? String ?? "Unknown" 68 | p["$app_version_string"] = infoDict["CFBundleShortVersionString"] as? String ?? "Unknown" 69 | 70 | p["mp_lib"] = "swift" 71 | p["$lib_version"] = AutomaticProperties.libVersion() 72 | p["$manufacturer"] = "Apple" 73 | p["$model"] = AutomaticProperties.deviceModel() 74 | 75 | return p 76 | }() 77 | 78 | static var peopleProperties: InternalProperties = { 79 | var p = InternalProperties() 80 | let infoDict = Bundle.main.infoDictionary 81 | if let infoDict = infoDict { 82 | p["$ios_app_version"] = infoDict["CFBundleVersion"] 83 | p["$ios_app_release"] = infoDict["CFBundleShortVersionString"] 84 | } 85 | p["$ios_device_model"] = AutomaticProperties.deviceModel() 86 | #if !os(OSX) && !os(watchOS) && !os(visionOS) 87 | p["$ios_version"] = UIDevice.current.systemVersion 88 | #else 89 | p["$ios_version"] = ProcessInfo.processInfo.operatingSystemVersionString 90 | #endif 91 | p["$ios_lib_version"] = AutomaticProperties.libVersion() 92 | p["$swift_lib_version"] = AutomaticProperties.libVersion() 93 | 94 | return p 95 | }() 96 | 97 | class func deviceModel() -> String { 98 | var modelCode: String = "Unknown" 99 | if AutomaticProperties.isiOSAppOnMac() { 100 | // iOS App Running on Apple Silicon Mac 101 | var size = 0 102 | sysctlbyname("hw.model", nil, &size, nil, 0) 103 | var model = [CChar](repeating: 0, count: size) 104 | sysctlbyname("hw.model", &model, &size, nil, 0) 105 | modelCode = String(cString: model) 106 | } else { 107 | var systemInfo = utsname() 108 | uname(&systemInfo) 109 | let size = MemoryLayout.size 110 | modelCode = withUnsafePointer(to: &systemInfo.machine) { 111 | $0.withMemoryRebound(to: CChar.self, capacity: size) { 112 | String(cString: UnsafePointer($0)) 113 | } 114 | } 115 | } 116 | return modelCode 117 | } 118 | 119 | #if os(watchOS) 120 | class func watchModel() -> String { 121 | let watchSize38mm = Int(136) 122 | let watchSize40mm = Int(162) 123 | let watchSize42mm = Int(156) 124 | let watchSize44mm = Int(184) 125 | 126 | let screenWidth = Int(WKInterfaceDevice.current().screenBounds.size.width) 127 | switch screenWidth { 128 | case watchSize38mm: 129 | return "Apple Watch 38mm" 130 | case watchSize40mm: 131 | return "Apple Watch 40mm" 132 | case watchSize42mm: 133 | return "Apple Watch 42mm" 134 | case watchSize44mm: 135 | return "Apple Watch 44mm" 136 | default: 137 | return "Apple Watch" 138 | } 139 | } 140 | #endif 141 | 142 | class func isiOSAppOnMac() -> Bool { 143 | var isiOSAppOnMac = false 144 | if #available(iOS 14.0, macOS 11.0, watchOS 7.0, tvOS 14.0, *) { 145 | isiOSAppOnMac = ProcessInfo.processInfo.isiOSAppOnMac 146 | } 147 | return isiOSAppOnMac 148 | } 149 | 150 | class func libVersion() -> String { 151 | return "5.1.0" 152 | } 153 | 154 | } 155 | -------------------------------------------------------------------------------- /Sources/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // Mixpanel 4 | // 5 | // Created by Yarden Eitan on 7/8/16. 6 | // Copyright © 2016 Mixpanel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if !os(OSX) 12 | import UIKit 13 | #endif // !os(OSX) 14 | 15 | struct QueueConstants { 16 | static var queueSize = 5000 17 | } 18 | 19 | struct APIConstants { 20 | static let maxBatchSize = 50 21 | static let flushSize = 1000 22 | static let minRetryBackoff = 60.0 23 | static let maxRetryBackoff = 600.0 24 | static let failuresTillBackoff = 2 25 | } 26 | 27 | struct BundleConstants { 28 | static let ID = "com.mixpanel.Mixpanel" 29 | } 30 | 31 | struct GzipSettings { 32 | static let gzipHeaderOffset = Int32(16) 33 | } 34 | 35 | #if !os(OSX) && !os(watchOS) && !os(visionOS) 36 | extension UIDevice { 37 | var iPhoneX: Bool { 38 | return UIScreen.main.nativeBounds.height == 2436 39 | } 40 | } 41 | #endif // !os(OSX) 42 | -------------------------------------------------------------------------------- /Sources/Data+Compression.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Data+Compression.swift 3 | // MixpanelSessionReplay 4 | // 5 | // Copyright © 2024 Mixpanel. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | import zlib 10 | 11 | public enum GzipError: Swift.Error { 12 | case stream 13 | case data 14 | case memory 15 | case buffer 16 | case version 17 | case unknown(code: Int) 18 | 19 | init(code: Int32) { 20 | switch code { 21 | case Z_STREAM_ERROR: 22 | self = .stream 23 | case Z_DATA_ERROR: 24 | self = .data 25 | case Z_MEM_ERROR: 26 | self = .memory 27 | case Z_BUF_ERROR: 28 | self = .buffer 29 | case Z_VERSION_ERROR: 30 | self = .version 31 | default: 32 | self = .unknown(code: Int(code)) 33 | } 34 | } 35 | } 36 | 37 | extension Data { 38 | /// Compresses the data using gzip compression. 39 | /// Adapted from: https://github.com/1024jp/GzipSwift/blob/main/Sources/Gzip/Data%2BGzip.swift 40 | /// - Parameter level: Compression level. 41 | /// - Returns: The compressed data. 42 | /// - Throws: `GzipError` if compression fails. 43 | public func gzipCompressed(level: Int32 = Z_DEFAULT_COMPRESSION) throws -> Data { 44 | guard !self.isEmpty else { 45 | MixpanelLogger.warn(message: "Empty Data object cannot be compressed.") 46 | return Data() 47 | } 48 | 49 | let originalSize = self.count 50 | 51 | var stream = z_stream() 52 | stream.next_in = UnsafeMutablePointer( 53 | mutating: (self as NSData).bytes.bindMemory(to: Bytef.self, capacity: self.count)) 54 | stream.avail_in = uint(self.count) 55 | 56 | let windowBits = MAX_WBITS + GzipSettings.gzipHeaderOffset // Use gzip header instead of zlib header 57 | let memLevel = MAX_MEM_LEVEL 58 | let strategy = Z_DEFAULT_STRATEGY 59 | 60 | var status = deflateInit2_( 61 | &stream, level, Z_DEFLATED, windowBits, memLevel, strategy, ZLIB_VERSION, 62 | Int32(MemoryLayout.size)) 63 | guard status == Z_OK else { 64 | throw GzipError(code: status) 65 | } 66 | 67 | var compressedData = Data(count: self.count / 2) 68 | repeat { 69 | if Int(stream.total_out) >= compressedData.count { 70 | compressedData.count += self.count / 2 71 | } 72 | let bufferPointer = compressedData.withUnsafeMutableBytes { 73 | $0.baseAddress?.assumingMemoryBound(to: Bytef.self) 74 | } 75 | guard let bufferPointer = bufferPointer else { 76 | throw GzipError(code: Z_BUF_ERROR) 77 | } 78 | stream.next_out = bufferPointer.advanced(by: Int(stream.total_out)) 79 | stream.avail_out = uint(compressedData.count) - uint(stream.total_out) 80 | 81 | status = deflate(&stream, Z_FINISH) 82 | } while stream.avail_out == 0 && status == Z_OK 83 | 84 | guard status == Z_STREAM_END else { 85 | throw GzipError(code: status) 86 | } 87 | 88 | deflateEnd(&stream) 89 | compressedData.count = Int(stream.total_out) 90 | 91 | let compressedSize = compressedData.count 92 | let compressionRatio = Double(compressedSize) / Double(originalSize) 93 | let compressionPercentage = (1 - compressionRatio) * 100 94 | 95 | let roundedCompressionRatio = floor(compressionRatio * 1000) / 1000 96 | let roundedCompressionPercentage = floor(compressionPercentage * 1000) / 1000 97 | 98 | MixpanelLogger.info( 99 | message: 100 | "Payload gzipped: original size = \(originalSize) bytes, compressed size = \(compressedSize) bytes, compression ratio = \(roundedCompressionRatio), compression percentage = \(roundedCompressionPercentage)%" 101 | ) 102 | 103 | return compressedData 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Sources/Error.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Error.swift 3 | // Mixpanel 4 | // 5 | // Created by Yarden Eitan on 6/10/16. 6 | // Copyright © 2016 Mixpanel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum PropertyError: Error { 12 | case invalidType(type: Any) 13 | } 14 | 15 | class Assertions { 16 | static var assertClosure = swiftAssertClosure 17 | static let swiftAssertClosure = { Swift.assert($0, $1, file: $2, line: $3) } 18 | } 19 | 20 | func MPAssert( 21 | _ condition: @autoclosure () -> Bool, 22 | _ message: @autoclosure () -> String = "", 23 | file: StaticString = #file, 24 | line: UInt = #line 25 | ) { 26 | Assertions.assertClosure(condition(), message(), file, line) 27 | } 28 | 29 | class ErrorHandler { 30 | class func wrap(_ f: () throws -> ReturnType?) -> ReturnType? { 31 | do { 32 | return try f() 33 | } catch let error { 34 | logError(error) 35 | return nil 36 | } 37 | } 38 | 39 | class func logError(_ error: Error) { 40 | let stackSymbols = Thread.callStackSymbols 41 | MixpanelLogger.error(message: "Error: \(error) \n Stack Symbols: \(stackSymbols)") 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /Sources/FileLogging.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileLogging.swift 3 | // MPLogger 4 | // 5 | // Created by Sam Green on 7/8/16. 6 | // Copyright © 2016 Mixpanel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Logs all messages to a file 12 | class FileLogging: MixpanelLogging { 13 | private let fileHandle: FileHandle 14 | 15 | init(path: String) { 16 | if let handle = FileHandle(forWritingAtPath: path) { 17 | fileHandle = handle 18 | } else { 19 | fileHandle = FileHandle.standardError 20 | } 21 | 22 | // Move to the end of the file so we can append messages 23 | fileHandle.seekToEndOfFile() 24 | } 25 | 26 | deinit { 27 | // Ensure we close the file handle to clear the resources 28 | fileHandle.closeFile() 29 | } 30 | 31 | func addMessage(message: MixpanelLogMessage) { 32 | let string = 33 | "File: \(message.file) - Func: \(message.function) - " 34 | + "Level: \(message.level.rawValue) - Message: \(message.text)" 35 | if let data = string.data(using: String.Encoding.utf8) { 36 | // Write the message as data to the file 37 | fileHandle.write(data) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/FlushRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlushRequest.swift 3 | // Mixpanel 4 | // 5 | // Created by Yarden Eitan on 7/8/16. 6 | // Copyright © 2016 Mixpanel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum FlushType: String { 12 | case events = "/track/" 13 | case people = "/engage/" 14 | case groups = "/groups/" 15 | } 16 | 17 | class FlushRequest: Network { 18 | 19 | var networkRequestsAllowedAfterTime = 0.0 20 | var networkConsecutiveFailures = 0 21 | 22 | func sendRequest( 23 | _ requestData: String, 24 | type: FlushType, 25 | useIP: Bool, 26 | headers: [String: String], 27 | queryItems: [URLQueryItem] = [], 28 | useGzipCompression: Bool 29 | ) -> Bool { 30 | 31 | let responseParser: (Data) -> Int? = { data in 32 | let response = String(data: data, encoding: String.Encoding.utf8) 33 | if let response = response { 34 | return Int(response) ?? 0 35 | } 36 | return nil 37 | } 38 | 39 | var resourceHeaders: [String: String] = ["Content-Type": "application/json"].merging(headers) { 40 | (_, new) in new 41 | } 42 | var compressedData: Data? = nil 43 | 44 | if useGzipCompression && type == .events { 45 | if let requestDataRaw = requestData.data(using: .utf8) { 46 | do { 47 | compressedData = try requestDataRaw.gzipCompressed() 48 | resourceHeaders["Content-Encoding"] = "gzip" 49 | } catch { 50 | MixpanelLogger.error(message: "Failed to compress data with gzip: \(error)") 51 | } 52 | } 53 | } 54 | let ipString = useIP ? "1" : "0" 55 | var resourceQueryItems: [URLQueryItem] = [URLQueryItem(name: "ip", value: ipString)] 56 | resourceQueryItems.append(contentsOf: queryItems) 57 | let resource = Network.buildResource( 58 | path: type.rawValue, 59 | method: .post, 60 | requestBody: compressedData ?? requestData.data(using: .utf8), 61 | queryItems: resourceQueryItems, 62 | headers: resourceHeaders, 63 | parse: responseParser) 64 | var result = false 65 | let semaphore = DispatchSemaphore(value: 0) 66 | flushRequestHandler( 67 | serverURL, 68 | resource: resource, 69 | completion: { success in 70 | result = success 71 | semaphore.signal() 72 | }) 73 | _ = semaphore.wait(timeout: .now() + 120.0) 74 | return result 75 | } 76 | 77 | private func flushRequestHandler( 78 | _ base: String, 79 | resource: Resource, 80 | completion: @escaping (Bool) -> Void 81 | ) { 82 | 83 | Network.apiRequest( 84 | base: base, resource: resource, 85 | failure: { (reason, _, response) in 86 | self.networkConsecutiveFailures += 1 87 | self.updateRetryDelay(response) 88 | MixpanelLogger.warn( 89 | message: "API request to \(resource.path) has failed with reason \(reason)") 90 | completion(false) 91 | }, 92 | success: { (result, response) in 93 | self.networkConsecutiveFailures = 0 94 | self.updateRetryDelay(response) 95 | if result == 0 { 96 | MixpanelLogger.info(message: "\(base) api rejected some items") 97 | } 98 | completion(true) 99 | }) 100 | } 101 | 102 | private func updateRetryDelay(_ response: URLResponse?) { 103 | var retryTime = 0.0 104 | let retryHeader = (response as? HTTPURLResponse)?.allHeaderFields["Retry-After"] as? String 105 | if let retryHeader = retryHeader, let retryHeaderParsed = (Double(retryHeader)) { 106 | retryTime = retryHeaderParsed 107 | } 108 | 109 | if networkConsecutiveFailures >= APIConstants.failuresTillBackoff { 110 | retryTime = max( 111 | retryTime, 112 | retryBackOffTimeWithConsecutiveFailures(networkConsecutiveFailures)) 113 | } 114 | let retryDate = Date(timeIntervalSinceNow: retryTime) 115 | networkRequestsAllowedAfterTime = retryDate.timeIntervalSince1970 116 | } 117 | 118 | private func retryBackOffTimeWithConsecutiveFailures(_ failureCount: Int) -> TimeInterval { 119 | let time = pow(2.0, Double(failureCount) - 1) * 60 + Double(arc4random_uniform(30)) 120 | return min( 121 | max(APIConstants.minRetryBackoff, time), 122 | APIConstants.maxRetryBackoff) 123 | } 124 | 125 | func requestNotAllowed() -> Bool { 126 | return Date().timeIntervalSince1970 < networkRequestsAllowedAfterTime 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /Sources/JSONHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONHandler.swift 3 | // Mixpanel 4 | // 5 | // Created by Yarden Eitan on 6/3/16. 6 | // Copyright © 2016 Mixpanel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class JSONHandler { 12 | 13 | typealias MPObjectToParse = Any 14 | 15 | class func encodeAPIData(_ obj: MPObjectToParse) -> String? { 16 | let data: Data? = serializeJSONObject(obj) 17 | 18 | guard let d = data else { 19 | MixpanelLogger.warn(message: "couldn't serialize object") 20 | return nil 21 | } 22 | 23 | return String(decoding: d, as: UTF8.self) 24 | } 25 | 26 | class func deserializeData(_ data: Data) -> MPObjectToParse? { 27 | var object: MPObjectToParse? 28 | do { 29 | object = try JSONSerialization.jsonObject(with: data, options: []) 30 | } catch { 31 | MixpanelLogger.warn(message: "exception decoding object data") 32 | } 33 | return object 34 | } 35 | 36 | class func serializeJSONObject(_ obj: MPObjectToParse) -> Data? { 37 | let serializableJSONObject: MPObjectToParse 38 | if let jsonObject = makeObjectSerializable(obj) as? [Any] { 39 | serializableJSONObject = jsonObject.filter { 40 | JSONSerialization.isValidJSONObject($0) 41 | } 42 | } else { 43 | serializableJSONObject = makeObjectSerializable(obj) 44 | } 45 | 46 | guard JSONSerialization.isValidJSONObject(serializableJSONObject) else { 47 | MixpanelLogger.warn(message: "object isn't valid and can't be serialzed to JSON") 48 | return nil 49 | } 50 | 51 | var serializedObject: Data? 52 | do { 53 | serializedObject = 54 | try JSONSerialization 55 | .data(withJSONObject: serializableJSONObject, options: []) 56 | } catch { 57 | MixpanelLogger.warn(message: "exception encoding api data") 58 | } 59 | return serializedObject 60 | } 61 | 62 | private class func makeObjectSerializable(_ obj: MPObjectToParse) -> MPObjectToParse { 63 | switch obj { 64 | case let obj as NSNumber: 65 | if isBoolNumber(obj) { 66 | return obj.boolValue 67 | } else if isInvalidNumber(obj) { 68 | return String(describing: obj) 69 | } else { 70 | return obj 71 | } 72 | 73 | case let obj as Double where obj.isFinite && !obj.isNaN: 74 | return obj 75 | 76 | case let obj as Float where obj.isFinite && !obj.isNaN: 77 | return obj 78 | 79 | case is String, is Int, is UInt, is UInt64, is Bool: 80 | return obj 81 | 82 | case let obj as [Any?]: 83 | // nil values in Array properties are dropped 84 | let nonNilEls: [Any] = obj.compactMap({ $0 }) 85 | return nonNilEls.map { makeObjectSerializable($0) } 86 | 87 | case let obj as [Any]: 88 | return obj.map { makeObjectSerializable($0) } 89 | 90 | case let obj as InternalProperties: 91 | var serializedDict = InternalProperties() 92 | _ = obj.map { e in 93 | serializedDict[e.key] = 94 | makeObjectSerializable(e.value) 95 | } 96 | return serializedDict 97 | 98 | case let obj as Date: 99 | let dateFormatter = DateFormatter() 100 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" 101 | dateFormatter.timeZone = TimeZone(abbreviation: "UTC") 102 | dateFormatter.locale = Locale(identifier: "en_US_POSIX") 103 | return dateFormatter.string(from: obj) 104 | 105 | case let obj as URL: 106 | return obj.absoluteString 107 | 108 | default: 109 | let objString = String(describing: obj) 110 | if objString == "nil" { 111 | // all nil properties outside of Arrays are converted to NSNull() 112 | return NSNull() 113 | } else { 114 | MixpanelLogger.info(message: "enforcing string on object") 115 | return objString 116 | } 117 | } 118 | } 119 | 120 | private class func isBoolNumber(_ num: NSNumber) -> Bool { 121 | let boolID = CFBooleanGetTypeID() 122 | let numID = CFGetTypeID(num) 123 | return numID == boolID 124 | } 125 | 126 | private class func isInvalidNumber(_ num: NSNumber) -> Bool { 127 | return num.doubleValue.isInfinite || num.doubleValue.isNaN 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Sources/Mixpanel/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyCollectedDataTypes 6 | 7 | NSPrivacyAccessedAPITypes 8 | 9 | 10 | NSPrivacyAccessedAPITypeReasons 11 | 12 | 1C8F.1 13 | 14 | NSPrivacyAccessedAPIType 15 | NSPrivacyAccessedAPICategoryUserDefaults 16 | 17 | 18 | NSPrivacyTrackingDomains 19 | 20 | NSPrivacyTracking 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Sources/MixpanelLogger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MixpanelLogger.swift 3 | // MixpanelLogger 4 | // 5 | // Created by Sam Green on 7/8/16. 6 | // Copyright © 2016 Mixpanel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// This defines the various levels of logging that a message may be tagged with. This allows hiding and 12 | /// showing different logging levels at run time depending on the environment 13 | public enum MixpanelLogLevel: String { 14 | /// MixpanelLogging displays *all* logs and additional debug information that may be useful to a developer 15 | case debug 16 | 17 | /// MixpanelLogging displays *all* logs (**except** debug) 18 | case info 19 | 20 | /// MixpanelLogging displays *only* warnings and above 21 | case warning 22 | 23 | /// MixpanelLogging displays *only* errors and above 24 | case error 25 | } 26 | 27 | /// This holds all the data for each log message, since the formatting is up to each 28 | /// logging object. It is a simple bag of data 29 | public struct MixpanelLogMessage { 30 | /// The file where this log message was created 31 | public let file: String 32 | 33 | /// The function where this log message was created 34 | public let function: String 35 | 36 | /// The text of the log message 37 | public let text: String 38 | 39 | /// The level of the log message 40 | public let level: MixpanelLogLevel 41 | 42 | init(path: String, function: String, text: String, level: MixpanelLogLevel) { 43 | if let file = path.components(separatedBy: "/").last { 44 | self.file = file 45 | } else { 46 | self.file = path 47 | } 48 | self.function = function 49 | self.text = text 50 | self.level = level 51 | } 52 | } 53 | 54 | /// Any object that conforms to this protocol may log messages 55 | public protocol MixpanelLogging { 56 | func addMessage(message: MixpanelLogMessage) 57 | } 58 | 59 | public class MixpanelLogger { 60 | private static var loggers = [MixpanelLogging]() 61 | private static var enabledLevels = Set() 62 | private static let readWriteLock: ReadWriteLock = ReadWriteLock(label: "loggerLock") 63 | 64 | /// Add a `MixpanelLogging` object to receive all log messages 65 | public class func addLogging(_ logging: MixpanelLogging) { 66 | readWriteLock.write { 67 | loggers.append(logging) 68 | } 69 | } 70 | 71 | /// Enable log messages of a specific `LogLevel` to be added to the log 72 | class func enableLevel(_ level: MixpanelLogLevel) { 73 | readWriteLock.write { 74 | enabledLevels.insert(level) 75 | } 76 | } 77 | 78 | /// Disable log messages of a specific `LogLevel` to prevent them from being logged 79 | class func disableLevel(_ level: MixpanelLogLevel) { 80 | readWriteLock.write { 81 | enabledLevels.remove(level) 82 | } 83 | } 84 | 85 | /// debug: Adds a debug message to the Mixpanel log 86 | /// - Parameter message: The message to be added to the log 87 | class func debug( 88 | message: @autoclosure () -> Any, _ path: String = #file, _ function: String = #function 89 | ) { 90 | var enabledLevels = Set() 91 | readWriteLock.read { 92 | enabledLevels = self.enabledLevels 93 | } 94 | guard enabledLevels.contains(.debug) else { return } 95 | forwardLogMessage( 96 | MixpanelLogMessage( 97 | path: path, function: function, text: "\(message())", 98 | level: .debug)) 99 | } 100 | 101 | /// info: Adds an informational message to the Mixpanel log 102 | /// - Parameter message: The message to be added to the log 103 | class func info( 104 | message: @autoclosure () -> Any, _ path: String = #file, _ function: String = #function 105 | ) { 106 | var enabledLevels = Set() 107 | readWriteLock.read { 108 | enabledLevels = self.enabledLevels 109 | } 110 | guard enabledLevels.contains(.info) else { return } 111 | forwardLogMessage( 112 | MixpanelLogMessage( 113 | path: path, function: function, text: "\(message())", 114 | level: .info)) 115 | } 116 | 117 | /// warn: Adds a warning message to the Mixpanel log 118 | /// - Parameter message: The message to be added to the log 119 | class func warn( 120 | message: @autoclosure () -> Any, _ path: String = #file, _ function: String = #function 121 | ) { 122 | var enabledLevels = Set() 123 | readWriteLock.read { 124 | enabledLevels = self.enabledLevels 125 | } 126 | guard enabledLevels.contains(.warning) else { return } 127 | forwardLogMessage( 128 | MixpanelLogMessage( 129 | path: path, function: function, text: "\(message())", 130 | level: .warning)) 131 | } 132 | 133 | /// error: Adds an error message to the Mixpanel log 134 | /// - Parameter message: The message to be added to the log 135 | class func error( 136 | message: @autoclosure () -> Any, _ path: String = #file, _ function: String = #function 137 | ) { 138 | var enabledLevels = Set() 139 | readWriteLock.read { 140 | enabledLevels = self.enabledLevels 141 | } 142 | guard enabledLevels.contains(.error) else { return } 143 | forwardLogMessage( 144 | MixpanelLogMessage( 145 | path: path, function: function, text: "\(message())", 146 | level: .error)) 147 | } 148 | 149 | /// This forwards a `MixpanelLogMessage` to each logger that has been added 150 | class private func forwardLogMessage(_ message: MixpanelLogMessage) { 151 | // Forward the log message to every registered MixpanelLogging instance 152 | var loggers = [MixpanelLogging]() 153 | readWriteLock.read { 154 | loggers = self.loggers 155 | } 156 | readWriteLock.write { 157 | loggers.forEach { $0.addMessage(message: message) } 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /Sources/MixpanelOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MixpanelOptions.swift 3 | // Mixpanel 4 | // 5 | // Created by Jared McFarland on 4/15/25. 6 | // Copyright © 2025 Mixpanel. All rights reserved. 7 | // 8 | 9 | public class MixpanelOptions { 10 | public let token: String 11 | public let flushInterval: Double 12 | public let instanceName: String? 13 | public let trackAutomaticEvents: Bool 14 | public let optOutTrackingByDefault: Bool 15 | public let useUniqueDistinctId: Bool 16 | public let superProperties: Properties? 17 | public let serverURL: String? 18 | public let proxyServerConfig: ProxyServerConfig? 19 | public let useGzipCompression: Bool 20 | public let featureFlagsEnabled: Bool 21 | public let featureFlagsContext: [String: Any] 22 | 23 | public init( 24 | token: String, 25 | flushInterval: Double = 60, 26 | instanceName: String? = nil, 27 | trackAutomaticEvents: Bool = false, 28 | optOutTrackingByDefault: Bool = false, 29 | useUniqueDistinctId: Bool = false, 30 | superProperties: Properties? = nil, 31 | serverURL: String? = nil, 32 | proxyServerConfig: ProxyServerConfig? = nil, 33 | useGzipCompression: Bool = true, // NOTE: This is a new default value! 34 | featureFlagsEnabled: Bool = false, 35 | featureFlagsContext: [String: Any] = [:] 36 | ) { 37 | self.token = token 38 | self.flushInterval = flushInterval 39 | self.instanceName = instanceName 40 | self.trackAutomaticEvents = trackAutomaticEvents 41 | self.optOutTrackingByDefault = optOutTrackingByDefault 42 | self.useUniqueDistinctId = useUniqueDistinctId 43 | self.superProperties = superProperties 44 | self.serverURL = serverURL 45 | self.proxyServerConfig = proxyServerConfig 46 | self.useGzipCompression = useGzipCompression 47 | self.featureFlagsEnabled = featureFlagsEnabled 48 | self.featureFlagsContext = featureFlagsContext 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/Network.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Network.swift 3 | // Mixpanel 4 | // 5 | // Created by Yarden Eitan on 6/2/16. 6 | // Copyright © 2016 Mixpanel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct BasePath { 12 | static let DefaultMixpanelAPI = "https://api.mixpanel.com" 13 | 14 | static func buildURL(base: String, path: String, queryItems: [URLQueryItem]?) -> URL? { 15 | guard let url = URL(string: base) else { 16 | return nil 17 | } 18 | guard var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { 19 | return nil 20 | } 21 | components.path += path 22 | components.queryItems = queryItems 23 | // adding workaround to replece + for %2B as it's not done by default within URLComponents 24 | components.percentEncodedQuery = components.percentEncodedQuery?.replacingOccurrences( 25 | of: "+", with: "%2B") 26 | return components.url 27 | } 28 | } 29 | 30 | enum RequestMethod: String { 31 | case get 32 | case post 33 | } 34 | 35 | struct Resource { 36 | let path: String 37 | let method: RequestMethod 38 | let requestBody: Data? 39 | let queryItems: [URLQueryItem]? 40 | let headers: [String: String] 41 | let parse: (Data) -> A? 42 | } 43 | 44 | enum Reason { 45 | case parseError 46 | case noData 47 | case notOKStatusCode(statusCode: Int) 48 | case other(Error) 49 | } 50 | 51 | public struct ServerProxyResource { 52 | public init(queryItems: [URLQueryItem]? = nil, headers: [String: String]) { 53 | self.queryItems = queryItems 54 | self.headers = headers 55 | } 56 | 57 | public let queryItems: [URLQueryItem]? 58 | public let headers: [String: String] 59 | } 60 | 61 | class Network { 62 | 63 | var serverURL: String 64 | 65 | required init(serverURL: String) { 66 | self.serverURL = serverURL 67 | } 68 | 69 | class func apiRequest( 70 | base: String, 71 | resource: Resource, 72 | failure: @escaping (Reason, Data?, URLResponse?) -> Void, 73 | success: @escaping (A, URLResponse?) -> Void 74 | ) { 75 | guard let request = buildURLRequest(base, resource: resource) else { 76 | return 77 | } 78 | 79 | URLSession.shared.dataTask(with: request) { (data, response, error) -> Void in 80 | guard let httpResponse = response as? HTTPURLResponse else { 81 | 82 | if let hasError = error { 83 | failure(.other(hasError), data, response) 84 | } else { 85 | failure(.noData, data, response) 86 | } 87 | return 88 | } 89 | guard httpResponse.statusCode == 200 else { 90 | failure(.notOKStatusCode(statusCode: httpResponse.statusCode), data, response) 91 | return 92 | } 93 | guard let responseData = data else { 94 | failure(.noData, data, response) 95 | return 96 | } 97 | guard let result = resource.parse(responseData) else { 98 | failure(.parseError, data, response) 99 | return 100 | } 101 | 102 | success(result, response) 103 | }.resume() 104 | } 105 | 106 | private class func buildURLRequest(_ base: String, resource: Resource) -> URLRequest? { 107 | guard 108 | let url = BasePath.buildURL( 109 | base: base, 110 | path: resource.path, 111 | queryItems: resource.queryItems) 112 | else { 113 | return nil 114 | } 115 | 116 | MixpanelLogger.debug(message: "Fetching URL") 117 | MixpanelLogger.debug(message: url.absoluteURL) 118 | var request = URLRequest(url: url) 119 | request.httpMethod = resource.method.rawValue 120 | request.httpBody = resource.requestBody 121 | 122 | for (k, v) in resource.headers { 123 | request.setValue(v, forHTTPHeaderField: k) 124 | } 125 | return request as URLRequest 126 | } 127 | 128 | class func buildResource( 129 | path: String, 130 | method: RequestMethod, 131 | requestBody: Data? = nil, 132 | queryItems: [URLQueryItem]? = nil, 133 | headers: [String: String], 134 | parse: @escaping (Data) -> A? 135 | ) -> Resource { 136 | return Resource( 137 | path: path, 138 | method: method, 139 | requestBody: requestBody, 140 | queryItems: queryItems, 141 | headers: headers, 142 | parse: parse) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /Sources/PrintLogging.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PrintLogging.swift 3 | // MPLogger 4 | // 5 | // Created by Sam Green on 7/8/16. 6 | // Copyright © 2016 Mixpanel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Simply formats and prints the object by calling `print` 12 | class PrintLogging: MixpanelLogging { 13 | func addMessage(message: MixpanelLogMessage) { 14 | print( 15 | "[Mixpanel - \(message.file) - func \(message.function)] (\(message.level.rawValue)) - \(message.text)" 16 | ) 17 | } 18 | } 19 | 20 | /// Simply formats and prints the object by calling `debugPrint`, this makes things a bit easier if you 21 | /// need to print data that may be quoted for instance. 22 | class PrintDebugLogging: MixpanelLogging { 23 | func addMessage(message: MixpanelLogMessage) { 24 | debugPrint( 25 | "[Mixpanel - \(message.file) - func \(message.function)] (\(message.level.rawValue)) - \(message.text)" 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/ReadWriteLock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReadWriteLock.swift 3 | // Mixpanel 4 | // 5 | // Created by Hairuo Sang on 8/9/17. 6 | // Copyright © 2017 Mixpanel. All rights reserved. 7 | // 8 | import Foundation 9 | 10 | class ReadWriteLock { 11 | private let concurrentQueue: DispatchQueue 12 | 13 | init(label: String) { 14 | concurrentQueue = DispatchQueue( 15 | label: label, qos: .utility, attributes: .concurrent, autoreleaseFrequency: .workItem) 16 | } 17 | 18 | func read(closure: () -> Void) { 19 | concurrentQueue.sync { 20 | closure() 21 | } 22 | } 23 | func write(closure: () -> T) -> T { 24 | concurrentQueue.sync( 25 | flags: .barrier, 26 | execute: { 27 | closure() 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/SessionMetadata.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Metadata.swift 3 | // Mixpanel 4 | // 5 | // Created by Yarden Eitan on 10/24/17. 6 | // Copyright © 2017 Mixpanel. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class SessionMetadata { 12 | var eventsCounter: UInt64 = 0 13 | var peopleCounter: UInt64 = 0 14 | var sessionID: String = String.randomId() 15 | var sessionStartEpoch: UInt64 = 0 16 | var trackingQueue: DispatchQueue 17 | 18 | init(trackingQueue: DispatchQueue) { 19 | self.trackingQueue = trackingQueue 20 | } 21 | 22 | func applicationWillEnterForeground() { 23 | trackingQueue.async { [weak self] in 24 | guard let self = self else { return } 25 | 26 | self.eventsCounter = 0 27 | self.peopleCounter = 0 28 | self.sessionID = String.randomId() 29 | self.sessionStartEpoch = UInt64(Date().timeIntervalSince1970) 30 | } 31 | } 32 | 33 | func toDict(isEvent: Bool = true) -> InternalProperties { 34 | let dict: [String: Any] = [ 35 | "$mp_metadata": [ 36 | "$mp_event_id": String.randomId(), 37 | "$mp_session_id": sessionID, 38 | "$mp_session_seq_id": (isEvent ? eventsCounter : peopleCounter), 39 | "$mp_session_start_sec": sessionStartEpoch, 40 | ] as [String: Any] 41 | ] 42 | isEvent ? (eventsCounter += 1) : (peopleCounter += 1) 43 | return dict 44 | } 45 | } 46 | 47 | extension String { 48 | fileprivate static func randomId() -> String { 49 | return String(format: "%08x%08x", arc4random(), arc4random()) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /docs/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | documentation 17 | 18 | 19 | documentation 20 | 21 | 22 | 68% 23 | 24 | 25 | 68% 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/css/highlight.css: -------------------------------------------------------------------------------- 1 | /*! Jazzy - https://github.com/realm/jazzy 2 | * Copyright Realm Inc. 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | /* Credit to https://gist.github.com/wataru420/2048287 */ 6 | .highlight .c { 7 | color: #999988; 8 | font-style: italic; } 9 | 10 | .highlight .err { 11 | color: #a61717; 12 | background-color: #e3d2d2; } 13 | 14 | .highlight .k { 15 | color: #000000; 16 | font-weight: bold; } 17 | 18 | .highlight .o { 19 | color: #000000; 20 | font-weight: bold; } 21 | 22 | .highlight .cm { 23 | color: #999988; 24 | font-style: italic; } 25 | 26 | .highlight .cp { 27 | color: #999999; 28 | font-weight: bold; } 29 | 30 | .highlight .c1 { 31 | color: #999988; 32 | font-style: italic; } 33 | 34 | .highlight .cs { 35 | color: #999999; 36 | font-weight: bold; 37 | font-style: italic; } 38 | 39 | .highlight .gd { 40 | color: #000000; 41 | background-color: #ffdddd; } 42 | 43 | .highlight .gd .x { 44 | color: #000000; 45 | background-color: #ffaaaa; } 46 | 47 | .highlight .ge { 48 | color: #000000; 49 | font-style: italic; } 50 | 51 | .highlight .gr { 52 | color: #aa0000; } 53 | 54 | .highlight .gh { 55 | color: #999999; } 56 | 57 | .highlight .gi { 58 | color: #000000; 59 | background-color: #ddffdd; } 60 | 61 | .highlight .gi .x { 62 | color: #000000; 63 | background-color: #aaffaa; } 64 | 65 | .highlight .go { 66 | color: #888888; } 67 | 68 | .highlight .gp { 69 | color: #555555; } 70 | 71 | .highlight .gs { 72 | font-weight: bold; } 73 | 74 | .highlight .gu { 75 | color: #aaaaaa; } 76 | 77 | .highlight .gt { 78 | color: #aa0000; } 79 | 80 | .highlight .kc { 81 | color: #000000; 82 | font-weight: bold; } 83 | 84 | .highlight .kd { 85 | color: #000000; 86 | font-weight: bold; } 87 | 88 | .highlight .kp { 89 | color: #000000; 90 | font-weight: bold; } 91 | 92 | .highlight .kr { 93 | color: #000000; 94 | font-weight: bold; } 95 | 96 | .highlight .kt { 97 | color: #445588; } 98 | 99 | .highlight .m { 100 | color: #009999; } 101 | 102 | .highlight .s { 103 | color: #d14; } 104 | 105 | .highlight .na { 106 | color: #008080; } 107 | 108 | .highlight .nb { 109 | color: #0086B3; } 110 | 111 | .highlight .nc { 112 | color: #445588; 113 | font-weight: bold; } 114 | 115 | .highlight .no { 116 | color: #008080; } 117 | 118 | .highlight .ni { 119 | color: #800080; } 120 | 121 | .highlight .ne { 122 | color: #990000; 123 | font-weight: bold; } 124 | 125 | .highlight .nf { 126 | color: #990000; } 127 | 128 | .highlight .nn { 129 | color: #555555; } 130 | 131 | .highlight .nt { 132 | color: #000080; } 133 | 134 | .highlight .nv { 135 | color: #008080; } 136 | 137 | .highlight .ow { 138 | color: #000000; 139 | font-weight: bold; } 140 | 141 | .highlight .w { 142 | color: #bbbbbb; } 143 | 144 | .highlight .mf { 145 | color: #009999; } 146 | 147 | .highlight .mh { 148 | color: #009999; } 149 | 150 | .highlight .mi { 151 | color: #009999; } 152 | 153 | .highlight .mo { 154 | color: #009999; } 155 | 156 | .highlight .sb { 157 | color: #d14; } 158 | 159 | .highlight .sc { 160 | color: #d14; } 161 | 162 | .highlight .sd { 163 | color: #d14; } 164 | 165 | .highlight .s2 { 166 | color: #d14; } 167 | 168 | .highlight .se { 169 | color: #d14; } 170 | 171 | .highlight .sh { 172 | color: #d14; } 173 | 174 | .highlight .si { 175 | color: #d14; } 176 | 177 | .highlight .sx { 178 | color: #d14; } 179 | 180 | .highlight .sr { 181 | color: #009926; } 182 | 183 | .highlight .s1 { 184 | color: #d14; } 185 | 186 | .highlight .ss { 187 | color: #990073; } 188 | 189 | .highlight .bp { 190 | color: #999999; } 191 | 192 | .highlight .vc { 193 | color: #008080; } 194 | 195 | .highlight .vg { 196 | color: #008080; } 197 | 198 | .highlight .vi { 199 | color: #008080; } 200 | 201 | .highlight .il { 202 | color: #009999; } 203 | -------------------------------------------------------------------------------- /docs/docsets/Mixpanel.docset/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.jazzy.mixpanel 7 | CFBundleName 8 | Mixpanel 9 | DocSetPlatformFamily 10 | mixpanel 11 | isDashDocset 12 | 13 | dashIndexFilePath 14 | index.html 15 | isJavaScriptEnabled 16 | 17 | DashDocSetFamily 18 | dashtoc 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/docsets/Mixpanel.docset/Contents/Resources/Documents/css/highlight.css: -------------------------------------------------------------------------------- 1 | /*! Jazzy - https://github.com/realm/jazzy 2 | * Copyright Realm Inc. 3 | * SPDX-License-Identifier: MIT 4 | */ 5 | /* Credit to https://gist.github.com/wataru420/2048287 */ 6 | .highlight .c { 7 | color: #999988; 8 | font-style: italic; } 9 | 10 | .highlight .err { 11 | color: #a61717; 12 | background-color: #e3d2d2; } 13 | 14 | .highlight .k { 15 | color: #000000; 16 | font-weight: bold; } 17 | 18 | .highlight .o { 19 | color: #000000; 20 | font-weight: bold; } 21 | 22 | .highlight .cm { 23 | color: #999988; 24 | font-style: italic; } 25 | 26 | .highlight .cp { 27 | color: #999999; 28 | font-weight: bold; } 29 | 30 | .highlight .c1 { 31 | color: #999988; 32 | font-style: italic; } 33 | 34 | .highlight .cs { 35 | color: #999999; 36 | font-weight: bold; 37 | font-style: italic; } 38 | 39 | .highlight .gd { 40 | color: #000000; 41 | background-color: #ffdddd; } 42 | 43 | .highlight .gd .x { 44 | color: #000000; 45 | background-color: #ffaaaa; } 46 | 47 | .highlight .ge { 48 | color: #000000; 49 | font-style: italic; } 50 | 51 | .highlight .gr { 52 | color: #aa0000; } 53 | 54 | .highlight .gh { 55 | color: #999999; } 56 | 57 | .highlight .gi { 58 | color: #000000; 59 | background-color: #ddffdd; } 60 | 61 | .highlight .gi .x { 62 | color: #000000; 63 | background-color: #aaffaa; } 64 | 65 | .highlight .go { 66 | color: #888888; } 67 | 68 | .highlight .gp { 69 | color: #555555; } 70 | 71 | .highlight .gs { 72 | font-weight: bold; } 73 | 74 | .highlight .gu { 75 | color: #aaaaaa; } 76 | 77 | .highlight .gt { 78 | color: #aa0000; } 79 | 80 | .highlight .kc { 81 | color: #000000; 82 | font-weight: bold; } 83 | 84 | .highlight .kd { 85 | color: #000000; 86 | font-weight: bold; } 87 | 88 | .highlight .kp { 89 | color: #000000; 90 | font-weight: bold; } 91 | 92 | .highlight .kr { 93 | color: #000000; 94 | font-weight: bold; } 95 | 96 | .highlight .kt { 97 | color: #445588; } 98 | 99 | .highlight .m { 100 | color: #009999; } 101 | 102 | .highlight .s { 103 | color: #d14; } 104 | 105 | .highlight .na { 106 | color: #008080; } 107 | 108 | .highlight .nb { 109 | color: #0086B3; } 110 | 111 | .highlight .nc { 112 | color: #445588; 113 | font-weight: bold; } 114 | 115 | .highlight .no { 116 | color: #008080; } 117 | 118 | .highlight .ni { 119 | color: #800080; } 120 | 121 | .highlight .ne { 122 | color: #990000; 123 | font-weight: bold; } 124 | 125 | .highlight .nf { 126 | color: #990000; } 127 | 128 | .highlight .nn { 129 | color: #555555; } 130 | 131 | .highlight .nt { 132 | color: #000080; } 133 | 134 | .highlight .nv { 135 | color: #008080; } 136 | 137 | .highlight .ow { 138 | color: #000000; 139 | font-weight: bold; } 140 | 141 | .highlight .w { 142 | color: #bbbbbb; } 143 | 144 | .highlight .mf { 145 | color: #009999; } 146 | 147 | .highlight .mh { 148 | color: #009999; } 149 | 150 | .highlight .mi { 151 | color: #009999; } 152 | 153 | .highlight .mo { 154 | color: #009999; } 155 | 156 | .highlight .sb { 157 | color: #d14; } 158 | 159 | .highlight .sc { 160 | color: #d14; } 161 | 162 | .highlight .sd { 163 | color: #d14; } 164 | 165 | .highlight .s2 { 166 | color: #d14; } 167 | 168 | .highlight .se { 169 | color: #d14; } 170 | 171 | .highlight .sh { 172 | color: #d14; } 173 | 174 | .highlight .si { 175 | color: #d14; } 176 | 177 | .highlight .sx { 178 | color: #d14; } 179 | 180 | .highlight .sr { 181 | color: #009926; } 182 | 183 | .highlight .s1 { 184 | color: #d14; } 185 | 186 | .highlight .ss { 187 | color: #990073; } 188 | 189 | .highlight .bp { 190 | color: #999999; } 191 | 192 | .highlight .vc { 193 | color: #008080; } 194 | 195 | .highlight .vg { 196 | color: #008080; } 197 | 198 | .highlight .vi { 199 | color: #008080; } 200 | 201 | .highlight .il { 202 | color: #009999; } 203 | -------------------------------------------------------------------------------- /docs/docsets/Mixpanel.docset/Contents/Resources/Documents/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/docs/docsets/Mixpanel.docset/Contents/Resources/Documents/img/carat.png -------------------------------------------------------------------------------- /docs/docsets/Mixpanel.docset/Contents/Resources/Documents/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/docs/docsets/Mixpanel.docset/Contents/Resources/Documents/img/dash.png -------------------------------------------------------------------------------- /docs/docsets/Mixpanel.docset/Contents/Resources/Documents/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/docs/docsets/Mixpanel.docset/Contents/Resources/Documents/img/gh.png -------------------------------------------------------------------------------- /docs/docsets/Mixpanel.docset/Contents/Resources/Documents/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/docs/docsets/Mixpanel.docset/Contents/Resources/Documents/img/spinner.gif -------------------------------------------------------------------------------- /docs/docsets/Mixpanel.docset/Contents/Resources/Documents/js/jazzy.js: -------------------------------------------------------------------------------- 1 | // Jazzy - https://github.com/realm/jazzy 2 | // Copyright Realm Inc. 3 | // SPDX-License-Identifier: MIT 4 | 5 | window.jazzy = {'docset': false} 6 | if (typeof window.dash != 'undefined') { 7 | document.documentElement.className += ' dash' 8 | window.jazzy.docset = true 9 | } 10 | if (navigator.userAgent.match(/xcode/i)) { 11 | document.documentElement.className += ' xcode' 12 | window.jazzy.docset = true 13 | } 14 | 15 | function toggleItem($link, $content) { 16 | var animationDuration = 300; 17 | $link.toggleClass('token-open'); 18 | $content.slideToggle(animationDuration); 19 | } 20 | 21 | function itemLinkToContent($link) { 22 | return $link.parent().parent().next(); 23 | } 24 | 25 | // On doc load + hash-change, open any targetted item 26 | function openCurrentItemIfClosed() { 27 | if (window.jazzy.docset) { 28 | return; 29 | } 30 | var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token'); 31 | $content = itemLinkToContent($link); 32 | if ($content.is(':hidden')) { 33 | toggleItem($link, $content); 34 | } 35 | } 36 | 37 | $(openCurrentItemIfClosed); 38 | $(window).on('hashchange', openCurrentItemIfClosed); 39 | 40 | // On item link ('token') click, toggle its discussion 41 | $('.token').on('click', function(event) { 42 | if (window.jazzy.docset) { 43 | return; 44 | } 45 | var $link = $(this); 46 | toggleItem($link, itemLinkToContent($link)); 47 | 48 | // Keeps the document from jumping to the hash. 49 | var href = $link.attr('href'); 50 | if (history.pushState) { 51 | history.pushState({}, '', href); 52 | } else { 53 | location.hash = href; 54 | } 55 | event.preventDefault(); 56 | }); 57 | 58 | // Clicks on links to the current, closed, item need to open the item 59 | $("a:not('.token')").on('click', function() { 60 | if (location == this.href) { 61 | openCurrentItemIfClosed(); 62 | } 63 | }); 64 | 65 | // KaTeX rendering 66 | if ("katex" in window) { 67 | $($('.math').each( (_, element) => { 68 | katex.render(element.textContent, element, { 69 | displayMode: $(element).hasClass('m-block'), 70 | throwOnError: false, 71 | trust: true 72 | }); 73 | })) 74 | } 75 | -------------------------------------------------------------------------------- /docs/docsets/Mixpanel.docset/Contents/Resources/Documents/js/jazzy.search.js: -------------------------------------------------------------------------------- 1 | // Jazzy - https://github.com/realm/jazzy 2 | // Copyright Realm Inc. 3 | // SPDX-License-Identifier: MIT 4 | 5 | $(function(){ 6 | var $typeahead = $('[data-typeahead]'); 7 | var $form = $typeahead.parents('form'); 8 | var searchURL = $form.attr('action'); 9 | 10 | function displayTemplate(result) { 11 | return result.name; 12 | } 13 | 14 | function suggestionTemplate(result) { 15 | var t = '
'; 16 | t += '' + result.name + ''; 17 | if (result.parent_name) { 18 | t += '' + result.parent_name + ''; 19 | } 20 | t += '
'; 21 | return t; 22 | } 23 | 24 | $typeahead.one('focus', function() { 25 | $form.addClass('loading'); 26 | 27 | $.getJSON(searchURL).then(function(searchData) { 28 | const searchIndex = lunr(function() { 29 | this.ref('url'); 30 | this.field('name'); 31 | this.field('abstract'); 32 | for (const [url, doc] of Object.entries(searchData)) { 33 | this.add({url: url, name: doc.name, abstract: doc.abstract}); 34 | } 35 | }); 36 | 37 | $typeahead.typeahead( 38 | { 39 | highlight: true, 40 | minLength: 3, 41 | autoselect: true 42 | }, 43 | { 44 | limit: 10, 45 | display: displayTemplate, 46 | templates: { suggestion: suggestionTemplate }, 47 | source: function(query, sync) { 48 | const lcSearch = query.toLowerCase(); 49 | const results = searchIndex.query(function(q) { 50 | q.term(lcSearch, { boost: 100 }); 51 | q.term(lcSearch, { 52 | boost: 10, 53 | wildcard: lunr.Query.wildcard.TRAILING 54 | }); 55 | }).map(function(result) { 56 | var doc = searchData[result.ref]; 57 | doc.url = result.ref; 58 | return doc; 59 | }); 60 | sync(results); 61 | } 62 | } 63 | ); 64 | $form.removeClass('loading'); 65 | $typeahead.trigger('focus'); 66 | }); 67 | }); 68 | 69 | var baseURL = searchURL.slice(0, -"search.json".length); 70 | 71 | $typeahead.on('typeahead:select', function(e, result) { 72 | window.location = baseURL + result.url; 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /docs/docsets/Mixpanel.docset/Contents/Resources/docSet.dsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/docs/docsets/Mixpanel.docset/Contents/Resources/docSet.dsidx -------------------------------------------------------------------------------- /docs/docsets/Mixpanel.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/docs/docsets/Mixpanel.tgz -------------------------------------------------------------------------------- /docs/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/docs/img/carat.png -------------------------------------------------------------------------------- /docs/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/docs/img/dash.png -------------------------------------------------------------------------------- /docs/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/docs/img/gh.png -------------------------------------------------------------------------------- /docs/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixpanel/mixpanel-swift/e8c8783d94f5fa2680f3e6eac202964c3db4d410/docs/img/spinner.gif -------------------------------------------------------------------------------- /docs/js/jazzy.js: -------------------------------------------------------------------------------- 1 | // Jazzy - https://github.com/realm/jazzy 2 | // Copyright Realm Inc. 3 | // SPDX-License-Identifier: MIT 4 | 5 | window.jazzy = {'docset': false} 6 | if (typeof window.dash != 'undefined') { 7 | document.documentElement.className += ' dash' 8 | window.jazzy.docset = true 9 | } 10 | if (navigator.userAgent.match(/xcode/i)) { 11 | document.documentElement.className += ' xcode' 12 | window.jazzy.docset = true 13 | } 14 | 15 | function toggleItem($link, $content) { 16 | var animationDuration = 300; 17 | $link.toggleClass('token-open'); 18 | $content.slideToggle(animationDuration); 19 | } 20 | 21 | function itemLinkToContent($link) { 22 | return $link.parent().parent().next(); 23 | } 24 | 25 | // On doc load + hash-change, open any targetted item 26 | function openCurrentItemIfClosed() { 27 | if (window.jazzy.docset) { 28 | return; 29 | } 30 | var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token'); 31 | $content = itemLinkToContent($link); 32 | if ($content.is(':hidden')) { 33 | toggleItem($link, $content); 34 | } 35 | } 36 | 37 | $(openCurrentItemIfClosed); 38 | $(window).on('hashchange', openCurrentItemIfClosed); 39 | 40 | // On item link ('token') click, toggle its discussion 41 | $('.token').on('click', function(event) { 42 | if (window.jazzy.docset) { 43 | return; 44 | } 45 | var $link = $(this); 46 | toggleItem($link, itemLinkToContent($link)); 47 | 48 | // Keeps the document from jumping to the hash. 49 | var href = $link.attr('href'); 50 | if (history.pushState) { 51 | history.pushState({}, '', href); 52 | } else { 53 | location.hash = href; 54 | } 55 | event.preventDefault(); 56 | }); 57 | 58 | // Clicks on links to the current, closed, item need to open the item 59 | $("a:not('.token')").on('click', function() { 60 | if (location == this.href) { 61 | openCurrentItemIfClosed(); 62 | } 63 | }); 64 | 65 | // KaTeX rendering 66 | if ("katex" in window) { 67 | $($('.math').each( (_, element) => { 68 | katex.render(element.textContent, element, { 69 | displayMode: $(element).hasClass('m-block'), 70 | throwOnError: false, 71 | trust: true 72 | }); 73 | })) 74 | } 75 | -------------------------------------------------------------------------------- /docs/js/jazzy.search.js: -------------------------------------------------------------------------------- 1 | // Jazzy - https://github.com/realm/jazzy 2 | // Copyright Realm Inc. 3 | // SPDX-License-Identifier: MIT 4 | 5 | $(function(){ 6 | var $typeahead = $('[data-typeahead]'); 7 | var $form = $typeahead.parents('form'); 8 | var searchURL = $form.attr('action'); 9 | 10 | function displayTemplate(result) { 11 | return result.name; 12 | } 13 | 14 | function suggestionTemplate(result) { 15 | var t = '
'; 16 | t += '' + result.name + ''; 17 | if (result.parent_name) { 18 | t += '' + result.parent_name + ''; 19 | } 20 | t += '
'; 21 | return t; 22 | } 23 | 24 | $typeahead.one('focus', function() { 25 | $form.addClass('loading'); 26 | 27 | $.getJSON(searchURL).then(function(searchData) { 28 | const searchIndex = lunr(function() { 29 | this.ref('url'); 30 | this.field('name'); 31 | this.field('abstract'); 32 | for (const [url, doc] of Object.entries(searchData)) { 33 | this.add({url: url, name: doc.name, abstract: doc.abstract}); 34 | } 35 | }); 36 | 37 | $typeahead.typeahead( 38 | { 39 | highlight: true, 40 | minLength: 3, 41 | autoselect: true 42 | }, 43 | { 44 | limit: 10, 45 | display: displayTemplate, 46 | templates: { suggestion: suggestionTemplate }, 47 | source: function(query, sync) { 48 | const lcSearch = query.toLowerCase(); 49 | const results = searchIndex.query(function(q) { 50 | q.term(lcSearch, { boost: 100 }); 51 | q.term(lcSearch, { 52 | boost: 10, 53 | wildcard: lunr.Query.wildcard.TRAILING 54 | }); 55 | }).map(function(result) { 56 | var doc = searchData[result.ref]; 57 | doc.url = result.ref; 58 | return doc; 59 | }); 60 | sync(results); 61 | } 62 | } 63 | ); 64 | $form.removeClass('loading'); 65 | $typeahead.trigger('focus'); 66 | }); 67 | }); 68 | 69 | var baseURL = searchURL.slice(0, -"search.json".length); 70 | 71 | $typeahead.on('typeahead:select', function(e, result) { 72 | window.location = baseURL + result.url; 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /scripts/carthage.sh: -------------------------------------------------------------------------------- 1 | export XCODE_XCCONFIG_FILE=$PWD/scripts/carthage.xcconfig 2 | carthage build --no-skip-current 3 | carthage archive Mixpanel 4 | -------------------------------------------------------------------------------- /scripts/carthage.xcconfig: -------------------------------------------------------------------------------- 1 | EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64=arm64 arm64e armv7 armv7s armv6 armv8 2 | EXCLUDED_ARCHS=$(inherited) $(EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_$(EFFECTIVE_PLATFORM_SUFFIX)__NATIVE_ARCH_64_BIT_$(NATIVE_ARCH_64_BIT)) -------------------------------------------------------------------------------- /scripts/generate_docs.sh: -------------------------------------------------------------------------------- 1 | jazzy \ 2 | --clean \ 3 | -a Mixpanel \ 4 | -u http://mixpanel.com \ 5 | --github_url https://github.com/mixpanel/mixpanel-swift \ 6 | --module-version 5.1.0 \ 7 | --framework-root . \ 8 | --module Mixpanel 9 | -------------------------------------------------------------------------------- /scripts/release.py: -------------------------------------------------------------------------------- 1 | 2 | import argparse 3 | import subprocess 4 | 5 | 6 | parser = argparse.ArgumentParser(description='Release Mixpanel Swift SDK') 7 | parser.add_argument('--old', help='version for the release', action="store") 8 | parser.add_argument('--new', help='version for the release', action="store") 9 | args = parser.parse_args() 10 | 11 | def bump_version(): 12 | replace_version('Mixpanel-swift.podspec', args.old, args.new) 13 | replace_version('Info.plist', args.old, args.new) 14 | replace_version('Sources/AutomaticProperties.swift', args.old, args.new) 15 | replace_version('./scripts/generate_docs.sh', args.old, args.new) 16 | subprocess.call('git add Mixpanel-swift.podspec', shell=True) 17 | subprocess.call('git add Info.plist', shell=True) 18 | subprocess.call('git add Sources/AutomaticProperties.swift', shell=True) 19 | subprocess.call('git add ./scripts/generate_docs.sh', shell=True) 20 | subprocess.call('git commit -m "Version {}"'.format(args.new), shell=True) 21 | subprocess.call('git push', shell=True) 22 | 23 | def replace_version(file_name, old_version, new_version): 24 | with open(file_name) as f: 25 | file_str = f.read() 26 | assert(old_version in file_str) 27 | file_str = file_str.replace(old_version, new_version) 28 | 29 | with open(file_name, "w") as f: 30 | f.write(file_str) 31 | 32 | def generate_docs(): 33 | subprocess.call('./scripts/generate_docs.sh', shell=True) 34 | subprocess.call('git add docs', shell=True) 35 | subprocess.call('git commit -m "Update docs"', shell=True) 36 | subprocess.call('git push', shell=True) 37 | 38 | def add_tag(): 39 | subprocess.call('git tag -a v{} -m "version {}"'.format(args.new, args.new), shell=True) 40 | subprocess.call('git push origin --tags', shell=True) 41 | 42 | def pushPod(): 43 | subprocess.call('sudo gem install cocoapods', shell=True) 44 | subprocess.call('pod trunk push Mixpanel-swift.podspec --allow-warnings', shell=True) 45 | 46 | def build_Carthage(): 47 | subprocess.call('./scripts/carthage.sh', shell=True) 48 | 49 | def main(): 50 | bump_version() 51 | generate_docs() 52 | add_tag() 53 | pushPod() 54 | build_Carthage() 55 | print("Congratulations, done!") 56 | 57 | if __name__ == '__main__': 58 | main() 59 | --------------------------------------------------------------------------------