diff --git a/.buildscript/bootstrap.sh b/.buildscript/bootstrap.sh deleted file mode 100755 index a2bf527ef..000000000 --- a/.buildscript/bootstrap.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash - -if ! which xcodebuild >/dev/null; then - echo "xcodebuild is not available. Install it from https://itunes.apple.com/us/app/xcode/id497799835" - exit 1 -else - echo "xcodebuild already installed" -fi - -if ! which gem >/dev/null; then - echo "rubygems is not available. Install it from https://rubygems.org/pages/download" - exit 1 -else - echo "rubygems already installed" -fi - -if ! which brew >/dev/null; then - echo "homebrew is not available. Install it from http://brew.sh" - exit 1 -else - echo "homebrew already installed" -fi - -if ! which pod >/dev/null; then - echo "installing cocoapods..." - gem install cocoapods -else - echo "cocoapods already installed" -fi - -if ! which xcpretty >/dev/null; then - echo "installing xcpretty." - gem install xcpretty -else - echo "xcpretty already installed" -fi - -if ! which xctool >/dev/null; then - echo "installing xctool." - brew install xctool -else - echo "xctool already installed" -fi - -echo "all dependencies installed." diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index b8c1df26d..000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,62 +0,0 @@ -version: 2 - -jobs: - build_and_test: - macos: - xcode: "11.3.1" - steps: - - checkout - - run: xcrun simctl list - - run: - name: Install build dependencies - command: | - sudo gem install xcpretty - sudo gem install cocoapods -v $(var=$(tail -1 Podfile.lock); echo ${var##COCOAPODS:}) - - - restore_cache: - key: 1-pods-{{ checksum "Podfile.lock" }} - - run: - name: Install CocoaPods - command: | - if [ ! -d "Pods" ] - then - make dependencies - fi - - save_cache: - key: 1-pods-{{ checksum "Podfile.lock" }} - paths: - - ./Pods - - - run: make build-ios - - run: make test-ios - - run: make build-tvos - - run: make test-tvos - - run: make lint - - run: make carthage - - - store_test_results: - # relies on xcpretty --report junit - path: build/reports - - run: bash <(curl -s https://codecov.io/bash) - - # Save Pods to artifacts in case we need to dig up dependencies later - - run: zip -FSr Pods.zip ./Pods - - store_artifacts: - path: Pods.zip - -workflows: - version: 2 - build_and_test: - jobs: - - build_and_test - scheduled_e2e_test: - triggers: - - schedule: - cron: "30 * * * *" - filters: - branches: - only: - - master - - scheduled_e2e_testing - jobs: - - build_and_test diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..fd89ad1d4 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @marandaneto diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..54021d95b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,41 @@ +name: 🐞 Bug Report +description: Tell us about something that's not working the way we (probably) intend. +labels: ["Platform: iOS", "bug"] +body: + + + - type: input + id: version + attributes: + label: Version + description: SDK Version + placeholder: 3.0.0 ← should look like this + validations: + required: true + + - type: textarea + id: repro + attributes: + label: Steps to Reproduce + description: How can we see what you're seeing? Specific is terrific. + placeholder: |- + 1. foo + 2. bar + 3. baz + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected Result + validations: + required: true + + - type: textarea + id: actual + attributes: + label: Actual Result + description: Logs? Screenshots? Yes, please. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..64d359ba1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Ask in the forums + url: https://posthog.com/questions + about: A place to ask questions. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..3ae03e2c4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,23 @@ +name: 💡 Feature Request +description: Tell us about a problem our SDK could solve but doesn't. +labels: ["enhancement"] +body: + - type: textarea + id: problem + attributes: + label: Problem Statement + description: What problem could we solve that it doesn't? + placeholder: |- + I want to make whirled peas, but it doesn't blend. + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Solution Brainstorm + description: We know you have bright ideas to share ... share away, friend. + placeholder: |- + Add a blender to it. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/maintainer-blank.yml b/.github/ISSUE_TEMPLATE/maintainer-blank.yml new file mode 100644 index 000000000..e120920ac --- /dev/null +++ b/.github/ISSUE_TEMPLATE/maintainer-blank.yml @@ -0,0 +1,10 @@ +name: Blank Issue +description: Blank Issue. Reserved for maintainers. +body: + - type: textarea + id: description + attributes: + label: Description + description: Please describe the issue. + validations: + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 2a7ecc687..0425157cd 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,16 +1,15 @@ -**What does this PR do?** +## :bulb: Motivation and Context + + -**Where should the reviewer start?** -**How should this be manually tested?** +## :green_heart: How did you test it? -**Any background context you want to provide?** -**What are the relevant tickets?** +## :pencil: Checklist + -**Screenshots or screencasts (if UI/UX change)** - -**Questions:** -- Does the docs need an update? -- Are there any security concerns? -- Do we need to update engineering / success? +- [ ] I reviewed the submitted code. +- [ ] I added tests to verify the changes. +- [ ] I updated the docs if needed. +- [ ] No breaking change or entry added to the changelog. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..b88a67a7f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: weekly diff --git a/.github/workflows/build-examples.yml b/.github/workflows/build-examples.yml new file mode 100644 index 000000000..2ffca4e35 --- /dev/null +++ b/.github/workflows/build-examples.yml @@ -0,0 +1,18 @@ +name: Build Examples +on: + push: + branches: + - main + pull_request: + paths-ignore: + - "**/*.md" +jobs: + build: + runs-on: macos-13 + steps: + - uses: actions/checkout@v4 + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '15.0' + - name: Build Example + run: make buildExample diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..d729c6cbc --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,18 @@ +name: Build +on: + push: + branches: + - main + pull_request: + paths-ignore: + - "**/*.md" +jobs: + build: + runs-on: macos-13 + steps: + - uses: actions/checkout@v4 + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '15.0' + - name: Build SDK + run: make buildSdk diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 6ad322be9..000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Build and test - - -on: - push: - branches: - - master - pull_request: - - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - build: - runs-on: macos-12 - steps: - - uses: actions/checkout@v1 - - run: ls /Applications - - name: Select Xcode - run: sudo xcode-select -switch /Applications/Xcode_14.2.app && /usr/bin/xcodebuild -version - - name: Install pods - run: pod install --repo-update - - name: Lint podspec - run: pod lib lint --no-clean - - - # tests: - # runs-on: macos-12 - # strategy: - # matrix: - # include: - # # Latest xcode - # - xcode: "14.2" - # ios: "16.2" - # # Older version - # - xcode: "13.1" - # ios: "15.0" - # name: test iOS (${{ matrix.ios }}) - # steps: - # - uses: actions/checkout@v1 - # - run: ls /Applications - # - name: Select Xcode - # run: sudo xcode-select -switch /Applications/Xcode_${{ matrix.xcode }}.app && /usr/bin/xcodebuild -version - # - name: Install pods - # run: pod install --repo-update - # - name: Run unit tests - # run: xcodebuild test -scheme PostHogTests -workspace PostHog.xcworkspace -destination 'platform=iOS Simulator,name=iPhone 13,OS=${{ matrix.ios }}' | xcpretty && exit ${PIPESTATUS[0]} diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml new file mode 100644 index 000000000..68e3645ba --- /dev/null +++ b/.github/workflows/danger.yml @@ -0,0 +1,16 @@ +name: "Danger" +on: + pull_request: + types: [opened, synchronize, reopened, edited, ready_for_review] + +jobs: + build: + name: Changelog + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - run: npx danger ci + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 000000000..0cd8b7397 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,16 @@ +name: Lint +on: + push: + branches: + - main + pull_request: + paths-ignore: + - "**/*.md" +jobs: + lint: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - name: Run lints + run: make lint diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..101878577 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,38 @@ +name: 'Release' +on: + release: + # runs for stable and pre-releases + types: [published] + +jobs: + cancel-previous-workflow: + runs-on: ubuntu-latest + steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@01ce38bf961b4e243a6342cbade0dbc8ba3f0432 # pin@0.12.0 + with: + access_token: ${{ github.token }} + + release: + name: Release + runs-on: macos-latest + env: + COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }} # Using Manoel's token for now + + steps: + - name: Git checkout + uses: actions/checkout@v4 + + - name: Install Cocoapods + run: gem install cocoapods + + - name: Update version + run: ./scripts/bump-version.sh ${{ github.event.release.tag_name }} + + - name: Commit & push + run: | + echo "Tag name from github.ref_name: ${{ github.event.release.target_commitish }}" + ./scripts/commit-code.sh ${{ github.event.release.target_commitish }} + + - name: Release + run: make releaseCocoaPods diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..ee7c8c8bf --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,18 @@ +name: Tests +on: + push: + branches: + - main + pull_request: + paths-ignore: + - "**/*.md" +jobs: + test: + runs-on: macos-13 + steps: + - uses: actions/checkout@v4 + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '15.0' + - name: Test SDK + run: make test diff --git a/.gitignore b/.gitignore index e674c2531..d3a8ff775 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,38 @@ -# OS X -.DS_Store +# Created by https://www.toptal.com/developers/gitignore/api/cocoapods,carthage,swiftpm,swiftpackagemanager,swift,xcode +# Edit at https://www.toptal.com/developers/gitignore?templates=cocoapods,carthage,swiftpm,swiftpackagemanager,swift,xcode + +### Carthage ### +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build + +### CocoaPods ### +## CocoaPods GitIgnore Template + +# CocoaPods - Only use to conserve bandwidth / Save time on Pushing +# - Also handy if you have a large number of dependant pods +# - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGNORE THE LOCK FILE +Pods/ +*.lock +### Swift ### # Xcode +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) build/ +DerivedData/ +*.moved-aside *.pbxuser !default.pbxuser *.mode1v3 @@ -11,27 +41,86 @@ build/ !default.mode2v3 *.perspectivev3 !default.perspectivev3 -xcuserdata/ -*.xccheckout -profile -*.moved-aside -DerivedData + +## Obj-C/Swift specific *.hmap -*.ipa -# Bundler -.bundle +## App packaging +*.ipa +*.dSYM.zip +*.dSYM -Carthage +## Playgrounds +timeline.xctimeline +playground.xcworkspace -# CocoaPods typically recommends against ignoring the Pods directory -# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control. -# However since we only use Pods for tests it's not as important to check dependencies in -# Instead we can archive the Pods repo for dependency backup purposes -# We don't need to check Pods in it for Carthage support because PostHog framework -# itself has no pod dependencies, only our tests do. -Pods/ -.clang-format -.idea +# Swift Package Manager +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +.swiftpm .build/ + +# CocoaPods +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# Add this line if you want to avoid checking in source code from the Xcode workspace +*.xcworkspace + +# Carthage +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + +### SwiftPackageManager ### +Packages +xcuserdata + + +### SwiftPM ### + + +### Xcode ### + +## Xcode 8 and earlier + +### Xcode Patch ### +# *.xcodeproj/* +# !*.xcodeproj/project.pbxproj +# !*.xcodeproj/xcshareddata/ +# !*.xcodeproj/project.xcworkspace/ +# !*.xcworkspace/contents.xcworkspacedata +# /*.gcno +# **/xcshareddata/WorkspaceSettings.xcsettings + +# End of https://www.toptal.com/developers/gitignore/api/cocoapods,carthage,swiftpm,swiftpackagemanager,swift,xcode + +.DS_Store diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 000000000..fc619f0f6 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,28 @@ +excluded: # case-sensitive paths to ignore during linting. Takes precedence over `included` + - PostHogExample + - PostHogExampleWithSPM + - PostHogTests + - PostHog/Utils/ReadWriteLock.swift + - PostHog/Utils/Reachability.swift + - PostHog/Utils/Data+Gzip.swift + - .build + - Pods + +disabled_rules: + - force_cast + - todo + - trailing_comma + - opening_brace + +line_length: 160 +file_length: + warning: 1000 + error: 1200 + +function_body_length: + - 1000 # warning + - 1200 # error + +type_body_length: + - 1000 # warning + - 1200 # error diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 9e26dfeeb..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index de7bced9f..2ed56c543 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ ## Next +## 3.0.0-alpha.2 - 2023-11-02 + +- Just testing the release automation + +## 3.0.0-alpha.1 - 2023-11-02 + +- First alpha of the new major version of the iOS SDK +- Just testing the release automation + ## 2.1.0 - 2023-10-10 - isFeatureEnabled now returns false if disabled [#74](https://github.com/PostHog/posthog-ios/pull/74) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..5e4487c67 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,17 @@ +# Contributing + +If you would like to contribute code to `posthog-ios` you can do so through +GitHub by forking the repository and opening a pull request against `main`. + +## Development Guide + +1. Install Xcode +2. Open the **file** `PostHog.xcodeproj` project in Xcode +3. Run `make bootstrap` to install all dependencies. + +When submitting code, please make every effort to follow existing conventions +and style in order to keep the code as readable as possible. Please also make +sure your code compiles by `make build`. In addition please consider adding +unit tests covering your change, this will make your change much more likely to be accepted + +Above all, thank you for contributing! diff --git a/Examples/CarthageExample/Cartfile b/Examples/CarthageExample/Cartfile deleted file mode 100644 index 4fa001fd2..000000000 --- a/Examples/CarthageExample/Cartfile +++ /dev/null @@ -1,3 +0,0 @@ -github "PostHog/posthog-ios" "1.0.2" -# Use a local project when debugging -# git "~/Projects/PostHog/posthog-ios/" "master" diff --git a/Examples/CarthageExample/Cartfile.resolved b/Examples/CarthageExample/Cartfile.resolved deleted file mode 100644 index 6a25514c7..000000000 --- a/Examples/CarthageExample/Cartfile.resolved +++ /dev/null @@ -1 +0,0 @@ -github "PostHog/posthog-ios" "1.0.2" diff --git a/Examples/CarthageExample/CarthageExample.xcodeproj/project.pbxproj b/Examples/CarthageExample/CarthageExample.xcodeproj/project.pbxproj deleted file mode 100644 index 9024d81bf..000000000 --- a/Examples/CarthageExample/CarthageExample.xcodeproj/project.pbxproj +++ /dev/null @@ -1,337 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - EAACFE571D25BFA400668199 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAACFE561D25BFA400668199 /* AppDelegate.swift */; }; - EAACFE591D25BFA400668199 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAACFE581D25BFA400668199 /* ViewController.swift */; }; - EAACFE5C1D25BFA400668199 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EAACFE5A1D25BFA400668199 /* Main.storyboard */; }; - EAACFE5E1D25BFA400668199 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EAACFE5D1D25BFA400668199 /* Assets.xcassets */; }; - EAACFE611D25BFA400668199 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EAACFE5F1D25BFA400668199 /* LaunchScreen.storyboard */; }; - EADEB9031DED3734005322DA /* PostHog.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EADEB9021DED3734005322DA /* PostHog.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - EAACFE531D25BFA400668199 /* CarthageExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CarthageExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; - EAACFE561D25BFA400668199 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - EAACFE581D25BFA400668199 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; - EAACFE5B1D25BFA400668199 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - EAACFE5D1D25BFA400668199 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - EAACFE601D25BFA400668199 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - EAACFE621D25BFA400668199 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - EADEB9021DED3734005322DA /* PostHog.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PostHog.framework; path = Carthage/Build/iOS/PostHog.framework; sourceTree = SOURCE_ROOT; }; - EADEB9041DED3740005322DA /* Cartfile.resolved */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Cartfile.resolved; sourceTree = SOURCE_ROOT; }; - EADEB9051DED3740005322DA /* Cartfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Cartfile; sourceTree = SOURCE_ROOT; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - EAACFE501D25BFA400668199 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - EADEB9031DED3734005322DA /* PostHog.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - EAACFE4A1D25BFA400668199 = { - isa = PBXGroup; - children = ( - EAACFE551D25BFA400668199 /* CarthageExample */, - EAACFE541D25BFA400668199 /* Products */, - ); - sourceTree = ""; - }; - EAACFE541D25BFA400668199 /* Products */ = { - isa = PBXGroup; - children = ( - EAACFE531D25BFA400668199 /* CarthageExample.app */, - ); - name = Products; - sourceTree = ""; - }; - EAACFE551D25BFA400668199 /* CarthageExample */ = { - isa = PBXGroup; - children = ( - EAACFE561D25BFA400668199 /* AppDelegate.swift */, - EAACFE581D25BFA400668199 /* ViewController.swift */, - EAACFE5A1D25BFA400668199 /* Main.storyboard */, - EAACFE5D1D25BFA400668199 /* Assets.xcassets */, - EAACFE5F1D25BFA400668199 /* LaunchScreen.storyboard */, - EAACFE621D25BFA400668199 /* Info.plist */, - EADEB9021DED3734005322DA /* PostHog.framework */, - EADEB9041DED3740005322DA /* Cartfile.resolved */, - EADEB9051DED3740005322DA /* Cartfile */, - ); - path = CarthageExample; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - EAACFE521D25BFA400668199 /* CarthageExample */ = { - isa = PBXNativeTarget; - buildConfigurationList = EAACFE651D25BFA400668199 /* Build configuration list for PBXNativeTarget "CarthageExample" */; - buildPhases = ( - EAACFE4F1D25BFA400668199 /* Sources */, - EAACFE501D25BFA400668199 /* Frameworks */, - EAACFE511D25BFA400668199 /* Resources */, - EAACFE6A1D25C28A00668199 /* Carthage Copy Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = CarthageExample; - productName = CarthageExample; - productReference = EAACFE531D25BFA400668199 /* CarthageExample.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - EAACFE4B1D25BFA400668199 /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 0810; - ORGANIZATIONNAME = PostHog; - TargetAttributes = { - EAACFE521D25BFA400668199 = { - CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 0810; - }; - }; - }; - buildConfigurationList = EAACFE4E1D25BFA400668199 /* Build configuration list for PBXProject "CarthageExample" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = EAACFE4A1D25BFA400668199; - productRefGroup = EAACFE541D25BFA400668199 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - EAACFE521D25BFA400668199 /* CarthageExample */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - EAACFE511D25BFA400668199 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - EAACFE611D25BFA400668199 /* LaunchScreen.storyboard in Resources */, - EAACFE5E1D25BFA400668199 /* Assets.xcassets in Resources */, - EAACFE5C1D25BFA400668199 /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - EAACFE6A1D25C28A00668199 /* Carthage Copy Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "$(SRCROOT)/Carthage/Build/iOS/PostHog.framework", - ); - name = "Carthage Copy Frameworks"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/usr/local/bin/carthage copy-frameworks"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - EAACFE4F1D25BFA400668199 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - EAACFE591D25BFA400668199 /* ViewController.swift in Sources */, - EAACFE571D25BFA400668199 /* AppDelegate.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - EAACFE5A1D25BFA400668199 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - EAACFE5B1D25BFA400668199 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - EAACFE5F1D25BFA400668199 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - EAACFE601D25BFA400668199 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - EAACFE631D25BFA400668199 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.0; - }; - name = Debug; - }; - EAACFE641D25BFA400668199 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 4.0; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - EAACFE661D25BFA400668199 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - INFOPLIST_FILE = CarthageExample/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.posthog.CarthageExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - EAACFE671D25BFA400668199 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - INFOPLIST_FILE = CarthageExample/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.posthog.CarthageExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - EAACFE4E1D25BFA400668199 /* Build configuration list for PBXProject "CarthageExample" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - EAACFE631D25BFA400668199 /* Debug */, - EAACFE641D25BFA400668199 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - EAACFE651D25BFA400668199 /* Build configuration list for PBXNativeTarget "CarthageExample" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - EAACFE661D25BFA400668199 /* Debug */, - EAACFE671D25BFA400668199 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = EAACFE4B1D25BFA400668199 /* Project object */; -} diff --git a/Examples/CarthageExample/CarthageExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Examples/CarthageExample/CarthageExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index ef35de35d..000000000 --- a/Examples/CarthageExample/CarthageExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/Examples/CarthageExample/CarthageExample/AppDelegate.swift b/Examples/CarthageExample/CarthageExample/AppDelegate.swift deleted file mode 100644 index 022dc8ba4..000000000 --- a/Examples/CarthageExample/CarthageExample/AppDelegate.swift +++ /dev/null @@ -1,48 +0,0 @@ -import UIKit -import PostHog - -// Use your own apiKey! -let PostHog = PHGPostHog(configuration: PHGPostHogConfiguration(apiKey: "_6SG-F7I1vCuZ-HdJL3VZQqjBlaSb1_20hDPwqMNnGI")) - -@UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate { - - var window: UIWindow? - - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { - - - // Override point for customization after application launch. - PostHog.capture("Carthage Example Launched") - PostHog.flush() - - PostHog.isFeatureEnabled("test-flag") - return true - } - - func applicationWillResignActive(_ application: UIApplication) { - // 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. - // 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. - } - - func applicationDidEnterBackground(_ application: UIApplication) { - // 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. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - } - - func applicationWillEnterForeground(_ application: UIApplication) { - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. - } - - func applicationDidBecomeActive(_ application: UIApplication) { - // 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. - } - - func applicationWillTerminate(_ application: UIApplication) { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. - } - - -} - diff --git a/Examples/CarthageExample/CarthageExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/Examples/CarthageExample/CarthageExample/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 81213230d..000000000 --- a/Examples/CarthageExample/CarthageExample/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "idiom" : "ios-marketing", - "scale" : "1x", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Examples/CarthageExample/CarthageExample/Base.lproj/LaunchScreen.storyboard b/Examples/CarthageExample/CarthageExample/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 2e721e183..000000000 --- a/Examples/CarthageExample/CarthageExample/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Examples/CarthageExample/CarthageExample/Base.lproj/Main.storyboard b/Examples/CarthageExample/CarthageExample/Base.lproj/Main.storyboard deleted file mode 100644 index 5f5b59bb7..000000000 --- a/Examples/CarthageExample/CarthageExample/Base.lproj/Main.storyboard +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Examples/CarthageExample/CarthageExample/Info.plist b/Examples/CarthageExample/CarthageExample/Info.plist deleted file mode 100644 index 6905cc67b..000000000 --- a/Examples/CarthageExample/CarthageExample/Info.plist +++ /dev/null @@ -1,40 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/Examples/CarthageExample/CarthageExample/ViewController.swift b/Examples/CarthageExample/CarthageExample/ViewController.swift deleted file mode 100644 index fc68fff76..000000000 --- a/Examples/CarthageExample/CarthageExample/ViewController.swift +++ /dev/null @@ -1,26 +0,0 @@ -import UIKit - -class ViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view, typically from a nib. - PostHog.capture("Carthage Main View Did Load") - PostHog.group("some-group", groupKey: "id:4", properties: [ - "company_name": "Awesome Inc" - ]); - PostHog.flush() - } - - override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() - // Dispose of any resources that can be recreated. - } - - @IBAction func fireEvent(_ sender: AnyObject) { - PostHog.capture("Carthage Button Pressed") - PostHog.flush() - } - -} - diff --git a/Examples/CarthageExample/Makefile b/Examples/CarthageExample/Makefile deleted file mode 100644 index d244fd48e..000000000 --- a/Examples/CarthageExample/Makefile +++ /dev/null @@ -1,7 +0,0 @@ - - -clean: - rm -rf Carthage - -build: - carthage update diff --git a/Examples/CocoapodsExample/CocoapodsExample.xcodeproj/project.pbxproj b/Examples/CocoapodsExample/CocoapodsExample.xcodeproj/project.pbxproj deleted file mode 100644 index 270b20f85..000000000 --- a/Examples/CocoapodsExample/CocoapodsExample.xcodeproj/project.pbxproj +++ /dev/null @@ -1,396 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 58DCE97D3F0B09E5B591D430 /* Pods_CocoapodsExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 013162A9EB31F4335B8BDC51 /* Pods_CocoapodsExample.framework */; }; - EADEB91E1DED460A005322DA /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = EADEB91D1DED460A005322DA /* main.m */; }; - EADEB9211DED460A005322DA /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = EADEB9201DED460A005322DA /* AppDelegate.m */; }; - EADEB9241DED460A005322DA /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = EADEB9231DED460A005322DA /* ViewController.m */; }; - EADEB9271DED460A005322DA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EADEB9251DED460A005322DA /* Main.storyboard */; }; - EADEB9291DED460A005322DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EADEB9281DED460A005322DA /* Assets.xcassets */; }; - EADEB92C1DED460A005322DA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EADEB92A1DED460A005322DA /* LaunchScreen.storyboard */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 013162A9EB31F4335B8BDC51 /* Pods_CocoapodsExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CocoapodsExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 3A7E1FAABB1A8D6E1490DF66 /* Pods-CocoapodsExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CocoapodsExample.release.xcconfig"; path = "Pods/Target Support Files/Pods-CocoapodsExample/Pods-CocoapodsExample.release.xcconfig"; sourceTree = ""; }; - 47C2E5ED764668C7B1DDFF73 /* Pods-CocoapodsExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CocoapodsExample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-CocoapodsExample/Pods-CocoapodsExample.debug.xcconfig"; sourceTree = ""; }; - EADEB9191DED460A005322DA /* CocoapodsExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CocoapodsExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; - EADEB91D1DED460A005322DA /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - EADEB91F1DED460A005322DA /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - EADEB9201DED460A005322DA /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - EADEB9221DED460A005322DA /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; - EADEB9231DED460A005322DA /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; - EADEB9261DED460A005322DA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - EADEB9281DED460A005322DA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - EADEB92B1DED460A005322DA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - EADEB92D1DED460A005322DA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - EADEB9161DED460A005322DA /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 58DCE97D3F0B09E5B591D430 /* Pods_CocoapodsExample.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 33AB7CD7B7188116825A8871 /* Pods */ = { - isa = PBXGroup; - children = ( - 47C2E5ED764668C7B1DDFF73 /* Pods-CocoapodsExample.debug.xcconfig */, - 3A7E1FAABB1A8D6E1490DF66 /* Pods-CocoapodsExample.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; - 8E167B7FDF57717E03FCE290 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 013162A9EB31F4335B8BDC51 /* Pods_CocoapodsExample.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - EADEB9101DED460A005322DA = { - isa = PBXGroup; - children = ( - EADEB91B1DED460A005322DA /* CocoapodsExample */, - EADEB91A1DED460A005322DA /* Products */, - 33AB7CD7B7188116825A8871 /* Pods */, - 8E167B7FDF57717E03FCE290 /* Frameworks */, - ); - sourceTree = ""; - }; - EADEB91A1DED460A005322DA /* Products */ = { - isa = PBXGroup; - children = ( - EADEB9191DED460A005322DA /* CocoapodsExample.app */, - ); - name = Products; - sourceTree = ""; - }; - EADEB91B1DED460A005322DA /* CocoapodsExample */ = { - isa = PBXGroup; - children = ( - EADEB91F1DED460A005322DA /* AppDelegate.h */, - EADEB9201DED460A005322DA /* AppDelegate.m */, - EADEB9221DED460A005322DA /* ViewController.h */, - EADEB9231DED460A005322DA /* ViewController.m */, - EADEB9251DED460A005322DA /* Main.storyboard */, - EADEB9281DED460A005322DA /* Assets.xcassets */, - EADEB92A1DED460A005322DA /* LaunchScreen.storyboard */, - EADEB92D1DED460A005322DA /* Info.plist */, - EADEB91C1DED460A005322DA /* Supporting Files */, - ); - path = CocoapodsExample; - sourceTree = ""; - }; - EADEB91C1DED460A005322DA /* Supporting Files */ = { - isa = PBXGroup; - children = ( - EADEB91D1DED460A005322DA /* main.m */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - EADEB9181DED460A005322DA /* CocoapodsExample */ = { - isa = PBXNativeTarget; - buildConfigurationList = EADEB9301DED460A005322DA /* Build configuration list for PBXNativeTarget "CocoapodsExample" */; - buildPhases = ( - 2748E9D3DD93358935ABDFAD /* [CP] Check Pods Manifest.lock */, - EADEB9151DED460A005322DA /* Sources */, - EADEB9161DED460A005322DA /* Frameworks */, - EADEB9171DED460A005322DA /* Resources */, - 29A9CC64339681809F2A93F1 /* [CP] Embed Pods Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = CocoapodsExample; - productName = CocoapodsExample; - productReference = EADEB9191DED460A005322DA /* CocoapodsExample.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - EADEB9111DED460A005322DA /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0900; - ORGANIZATIONNAME = PostHog; - TargetAttributes = { - EADEB9181DED460A005322DA = { - CreatedOnToolsVersion = 8.1; - ProvisioningStyle = Automatic; - }; - }; - }; - buildConfigurationList = EADEB9141DED460A005322DA /* Build configuration list for PBXProject "CocoapodsExample" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = EADEB9101DED460A005322DA; - productRefGroup = EADEB91A1DED460A005322DA /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - EADEB9181DED460A005322DA /* CocoapodsExample */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - EADEB9171DED460A005322DA /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - EADEB92C1DED460A005322DA /* LaunchScreen.storyboard in Resources */, - EADEB9291DED460A005322DA /* Assets.xcassets in Resources */, - EADEB9271DED460A005322DA /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 2748E9D3DD93358935ABDFAD /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-CocoapodsExample-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 29A9CC64339681809F2A93F1 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-CocoapodsExample/Pods-CocoapodsExample-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/PostHog/PostHog.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PostHog.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-CocoapodsExample/Pods-CocoapodsExample-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - EADEB9151DED460A005322DA /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - EADEB9241DED460A005322DA /* ViewController.m in Sources */, - EADEB9211DED460A005322DA /* AppDelegate.m in Sources */, - EADEB91E1DED460A005322DA /* main.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - EADEB9251DED460A005322DA /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - EADEB9261DED460A005322DA /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - EADEB92A1DED460A005322DA /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - EADEB92B1DED460A005322DA /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - EADEB92E1DED460A005322DA /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_SUSPICIOUS_MOVES = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - }; - name = Debug; - }; - EADEB92F1DED460A005322DA /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_SUSPICIOUS_MOVES = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - EADEB9311DED460A005322DA /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 47C2E5ED764668C7B1DDFF73 /* Pods-CocoapodsExample.debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - INFOPLIST_FILE = CocoapodsExample/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.posthog.CocoapodsExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - EADEB9321DED460A005322DA /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 3A7E1FAABB1A8D6E1490DF66 /* Pods-CocoapodsExample.release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - INFOPLIST_FILE = CocoapodsExample/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.posthog.CocoapodsExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - EADEB9141DED460A005322DA /* Build configuration list for PBXProject "CocoapodsExample" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - EADEB92E1DED460A005322DA /* Debug */, - EADEB92F1DED460A005322DA /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - EADEB9301DED460A005322DA /* Build configuration list for PBXNativeTarget "CocoapodsExample" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - EADEB9311DED460A005322DA /* Debug */, - EADEB9321DED460A005322DA /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = EADEB9111DED460A005322DA /* Project object */; -} diff --git a/Examples/CocoapodsExample/CocoapodsExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Examples/CocoapodsExample/CocoapodsExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 980311595..000000000 --- a/Examples/CocoapodsExample/CocoapodsExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/Examples/CocoapodsExample/CocoapodsExample.xcworkspace/contents.xcworkspacedata b/Examples/CocoapodsExample/CocoapodsExample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 2087b1f8e..000000000 --- a/Examples/CocoapodsExample/CocoapodsExample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/Examples/CocoapodsExample/CocoapodsExample/AppDelegate.h b/Examples/CocoapodsExample/CocoapodsExample/AppDelegate.h deleted file mode 100644 index 1de344e20..000000000 --- a/Examples/CocoapodsExample/CocoapodsExample/AppDelegate.h +++ /dev/null @@ -1,9 +0,0 @@ -#import - - -@interface AppDelegate : UIResponder - -@property (strong, nonatomic) UIWindow *window; - - -@end diff --git a/Examples/CocoapodsExample/CocoapodsExample/AppDelegate.m b/Examples/CocoapodsExample/CocoapodsExample/AppDelegate.m deleted file mode 100644 index b578d87ef..000000000 --- a/Examples/CocoapodsExample/CocoapodsExample/AppDelegate.m +++ /dev/null @@ -1,63 +0,0 @@ -#import -#import "AppDelegate.h" - - -@interface AppDelegate () - -@end - -NSString *const POSTHOG_API_KEY = @"_6SG-F7I1vCuZ-HdJL3VZQqjBlaSb1_20hDPwqMNnGI"; - - -@implementation AppDelegate - - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions -{ - [PHGPostHog debug:YES]; - PHGPostHogConfiguration *configuration = [PHGPostHogConfiguration configurationWithApiKey:POSTHOG_API_KEY host:@"https://app.posthog.com"]; - configuration.captureApplicationLifecycleEvents = YES; - configuration.recordScreenViews = YES; - configuration.flushAt = 1; - [PHGPostHog setupWithConfiguration:configuration]; - [[PHGPostHog sharedPostHog] identify:@"Prateek" properties:nil options: @{@"$anon_distinct_id":@"test_anonymousId"}]; - [[PHGPostHog sharedPostHog] capture:@"Cocoapods Example Launched"]; - - [[PHGPostHog sharedPostHog] group:@"some-group" groupKey:@"id:4" properties: @{@"company_name":@"Awesome Inc"}]; - - [[PHGPostHog sharedPostHog] flush]; - NSLog(@"application:didFinishLaunchingWithOptions: %@", launchOptions); - return YES; -} - - -- (void)applicationWillResignActive:(UIApplication *)application -{ - NSLog(@"applicationWillResignActive:"); -} - - -- (void)applicationDidEnterBackground:(UIApplication *)application -{ - NSLog(@"applicationDidEnterBackground:"); -} - - -- (void)applicationWillEnterForeground:(UIApplication *)application -{ - NSLog(@"applicationWillEnterForeground:"); -} - - -- (void)applicationDidBecomeActive:(UIApplication *)application -{ - NSLog(@"applicationDidBecomeActive:"); -} - - -- (void)applicationWillTerminate:(UIApplication *)application -{ - NSLog(@"applicationWillTerminate:"); -} - -@end diff --git a/Examples/CocoapodsExample/CocoapodsExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/Examples/CocoapodsExample/CocoapodsExample/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index b8236c653..000000000 --- a/Examples/CocoapodsExample/CocoapodsExample/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "size" : "20x20", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "20x20", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Examples/CocoapodsExample/CocoapodsExample/Base.lproj/Main.storyboard b/Examples/CocoapodsExample/CocoapodsExample/Base.lproj/Main.storyboard deleted file mode 100644 index ed0a60c5c..000000000 --- a/Examples/CocoapodsExample/CocoapodsExample/Base.lproj/Main.storyboard +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Examples/CocoapodsExample/CocoapodsExample/Info.plist b/Examples/CocoapodsExample/CocoapodsExample/Info.plist deleted file mode 100644 index 195a3fafe..000000000 --- a/Examples/CocoapodsExample/CocoapodsExample/Info.plist +++ /dev/null @@ -1,51 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLName - posthog.cocoapodsexample - CFBundleURLSchemes - - posthog-cocoapods - - - - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/Examples/CocoapodsExample/CocoapodsExample/ViewController.h b/Examples/CocoapodsExample/CocoapodsExample/ViewController.h deleted file mode 100644 index db4e1ecda..000000000 --- a/Examples/CocoapodsExample/CocoapodsExample/ViewController.h +++ /dev/null @@ -1,7 +0,0 @@ -#import - - -@interface ViewController : UIViewController - - -@end diff --git a/Examples/CocoapodsExample/CocoapodsExample/ViewController.m b/Examples/CocoapodsExample/CocoapodsExample/ViewController.m deleted file mode 100644 index eb5f0879f..000000000 --- a/Examples/CocoapodsExample/CocoapodsExample/ViewController.m +++ /dev/null @@ -1,46 +0,0 @@ -#import -// TODO: Test and see if this works -// @import PostHog; -#import "ViewController.h" - - -@interface ViewController () - -@end - - -@implementation ViewController - -- (void)viewDidLoad -{ - [super viewDidLoad]; - // Do any additional setup after loading the view, typically from a nib. - NSUserActivity *userActivity = [[NSUserActivity alloc] initWithActivityType:NSUserActivityTypeBrowsingWeb]; - userActivity.webpageURL = [NSURL URLWithString:@"http://www.posthog.com"]; - [[PHGPostHog sharedPostHog] continueUserActivity:userActivity]; - [[PHGPostHog sharedPostHog] capture:@"test"]; - [[PHGPostHog sharedPostHog] flush]; - - [[PHGPostHog sharedPostHog] isFeatureEnabled:@"test-flag"]; -} - -- (IBAction)fireEvent:(id)sender -{ - [[PHGPostHog sharedPostHog] capture:@"Cocoapods Example Button"]; - [[PHGPostHog sharedPostHog] flush]; -// NSMutableDictionary *payload = [[NSMutableDictionary alloc] init]; -// [[PHGPostHog sharedPostHog] handleActionWithIdentifier:(@"identifier") -// forRemoteNotification:(payload)]; -// [[PHGPostHog sharedPostHog] receivedRemoteNotification:(payload)]; -// NSError * myInternalError = [NSError errorWithDomain:@"com.myerror" code:1 userInfo:payload]; -// [[PHGPostHog sharedPostHog] failedToRegisterForRemoteNotificationsWithError:(myInternalError)]; -} - -- (void)didReceiveMemoryWarning -{ - [super didReceiveMemoryWarning]; - // Dispose of any resources that can be recreated. -} - - -@end diff --git a/Examples/CocoapodsExample/CocoapodsExample/main.m b/Examples/CocoapodsExample/CocoapodsExample/main.m deleted file mode 100644 index 42efc4fe1..000000000 --- a/Examples/CocoapodsExample/CocoapodsExample/main.m +++ /dev/null @@ -1,10 +0,0 @@ -#import -#import "AppDelegate.h" - -int main(int argc, char *argv[]) -{ - @autoreleasepool - { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -} diff --git a/Examples/CocoapodsExample/Podfile b/Examples/CocoapodsExample/Podfile deleted file mode 100644 index 80cec5269..000000000 --- a/Examples/CocoapodsExample/Podfile +++ /dev/null @@ -1,7 +0,0 @@ -use_frameworks! - -target 'CocoapodsExample' do - pod 'PostHog', :path => '../../' -end - - diff --git a/Examples/CocoapodsExample/Podfile.lock b/Examples/CocoapodsExample/Podfile.lock deleted file mode 100644 index ba635a459..000000000 --- a/Examples/CocoapodsExample/Podfile.lock +++ /dev/null @@ -1,16 +0,0 @@ -PODS: - - PostHog (2.0.3) - -DEPENDENCIES: - - PostHog (from `../../`) - -EXTERNAL SOURCES: - PostHog: - :path: "../../" - -SPEC CHECKSUMS: - PostHog: 68d0afcbb8ea8c9a1ce30b9fa20a6ef75cd3c6f6 - -PODFILE CHECKSUM: d8ae0242089957a348baa3a1323724fc5f04d458 - -COCOAPODS: 1.12.1 diff --git a/Examples/ManualExample/ManualExample.xcodeproj/project.pbxproj b/Examples/ManualExample/ManualExample.xcodeproj/project.pbxproj deleted file mode 100644 index 5a177e571..000000000 --- a/Examples/ManualExample/ManualExample.xcodeproj/project.pbxproj +++ /dev/null @@ -1,387 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - EAACFE991D25CEFD00668199 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = EAACFE981D25CEFD00668199 /* main.m */; }; - EAACFE9C1D25CEFD00668199 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = EAACFE9B1D25CEFD00668199 /* AppDelegate.m */; }; - EAACFE9F1D25CEFD00668199 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = EAACFE9E1D25CEFD00668199 /* ViewController.m */; }; - EAACFEA21D25CEFD00668199 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EAACFEA01D25CEFD00668199 /* Main.storyboard */; }; - EAACFEA41D25CEFD00668199 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EAACFEA31D25CEFD00668199 /* Assets.xcassets */; }; - EAACFEA71D25CEFD00668199 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EAACFEA51D25CEFD00668199 /* LaunchScreen.storyboard */; }; - EADEB90F1DED404D005322DA /* PostHog.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EAACFEB51D25CF1000668199 /* PostHog.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - EAACFEB41D25CF1000668199 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = EAACFEAE1D25CF1000668199 /* PostHog.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = EAACFE271D25BCED00668199; - remoteInfo = PostHog; - }; - EADEB90B1DED4005005322DA /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = EAACFEAE1D25CF1000668199 /* PostHog.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = EADEB86A1DECD0EF005322DA; - remoteInfo = PostHogTests; - }; - EADEB90D1DED4047005322DA /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = EAACFEAE1D25CF1000668199 /* PostHog.xcodeproj */; - proxyType = 1; - remoteGlobalIDString = EADEB85A1DECD080005322DA; - remoteInfo = PostHog; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - EAACFE941D25CEFD00668199 /* ManualExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ManualExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; - EAACFE981D25CEFD00668199 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - EAACFE9A1D25CEFD00668199 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - EAACFE9B1D25CEFD00668199 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - EAACFE9D1D25CEFD00668199 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; - EAACFE9E1D25CEFD00668199 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; - EAACFEA11D25CEFD00668199 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - EAACFEA31D25CEFD00668199 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - EAACFEA61D25CEFD00668199 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - EAACFEA81D25CEFD00668199 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - EAACFEAE1D25CF1000668199 /* PostHog.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PostHog.xcodeproj; path = ../../../PostHog.xcodeproj; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - EAACFE911D25CEFD00668199 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - EADEB90F1DED404D005322DA /* PostHog.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - EAACFE8B1D25CEFD00668199 = { - isa = PBXGroup; - children = ( - EAACFE961D25CEFD00668199 /* ManualExample */, - EAACFE951D25CEFD00668199 /* Products */, - ); - sourceTree = ""; - }; - EAACFE951D25CEFD00668199 /* Products */ = { - isa = PBXGroup; - children = ( - EAACFE941D25CEFD00668199 /* ManualExample.app */, - ); - name = Products; - sourceTree = ""; - }; - EAACFE961D25CEFD00668199 /* ManualExample */ = { - isa = PBXGroup; - children = ( - EAACFEAE1D25CF1000668199 /* PostHog.xcodeproj */, - EAACFE9A1D25CEFD00668199 /* AppDelegate.h */, - EAACFE9B1D25CEFD00668199 /* AppDelegate.m */, - EAACFE9D1D25CEFD00668199 /* ViewController.h */, - EAACFE9E1D25CEFD00668199 /* ViewController.m */, - EAACFEA01D25CEFD00668199 /* Main.storyboard */, - EAACFEA31D25CEFD00668199 /* Assets.xcassets */, - EAACFEA51D25CEFD00668199 /* LaunchScreen.storyboard */, - EAACFEA81D25CEFD00668199 /* Info.plist */, - EAACFE971D25CEFD00668199 /* Supporting Files */, - ); - path = ManualExample; - sourceTree = ""; - }; - EAACFE971D25CEFD00668199 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - EAACFE981D25CEFD00668199 /* main.m */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - EAACFEAF1D25CF1000668199 /* Products */ = { - isa = PBXGroup; - children = ( - EAACFEB51D25CF1000668199 /* PostHog.framework */, - EADEB90C1DED4005005322DA /* PostHogTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - EAACFE931D25CEFD00668199 /* ManualExample */ = { - isa = PBXNativeTarget; - buildConfigurationList = EAACFEAB1D25CEFD00668199 /* Build configuration list for PBXNativeTarget "ManualExample" */; - buildPhases = ( - EAACFE901D25CEFD00668199 /* Sources */, - EAACFE911D25CEFD00668199 /* Frameworks */, - EAACFE921D25CEFD00668199 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - EADEB90E1DED4047005322DA /* PBXTargetDependency */, - ); - name = ManualExample; - productName = ManualExample; - productReference = EAACFE941D25CEFD00668199 /* ManualExample.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - EAACFE8C1D25CEFD00668199 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0810; - ORGANIZATIONNAME = PostHog; - TargetAttributes = { - EAACFE931D25CEFD00668199 = { - CreatedOnToolsVersion = 7.3.1; - }; - }; - }; - buildConfigurationList = EAACFE8F1D25CEFD00668199 /* Build configuration list for PBXProject "ManualExample" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = EAACFE8B1D25CEFD00668199; - productRefGroup = EAACFE951D25CEFD00668199 /* Products */; - projectDirPath = ""; - projectReferences = ( - { - ProductGroup = EAACFEAF1D25CF1000668199 /* Products */; - ProjectRef = EAACFEAE1D25CF1000668199 /* PostHog.xcodeproj */; - }, - ); - projectRoot = ""; - targets = ( - EAACFE931D25CEFD00668199 /* ManualExample */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXReferenceProxy section */ - EAACFEB51D25CF1000668199 /* PostHog.framework */ = { - isa = PBXReferenceProxy; - fileType = wrapper.framework; - path = PostHog.framework; - remoteRef = EAACFEB41D25CF1000668199 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - EADEB90C1DED4005005322DA /* PostHogTests.xctest */ = { - isa = PBXReferenceProxy; - fileType = wrapper.cfbundle; - path = PostHogTests.xctest; - remoteRef = EADEB90B1DED4005005322DA /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; -/* End PBXReferenceProxy section */ - -/* Begin PBXResourcesBuildPhase section */ - EAACFE921D25CEFD00668199 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - EAACFEA71D25CEFD00668199 /* LaunchScreen.storyboard in Resources */, - EAACFEA41D25CEFD00668199 /* Assets.xcassets in Resources */, - EAACFEA21D25CEFD00668199 /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - EAACFE901D25CEFD00668199 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - EAACFE9F1D25CEFD00668199 /* ViewController.m in Sources */, - EAACFE9C1D25CEFD00668199 /* AppDelegate.m in Sources */, - EAACFE991D25CEFD00668199 /* main.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - EADEB90E1DED4047005322DA /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = PostHog; - targetProxy = EADEB90D1DED4047005322DA /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - EAACFEA01D25CEFD00668199 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - EAACFEA11D25CEFD00668199 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - EAACFEA51D25CEFD00668199 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - EAACFEA61D25CEFD00668199 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - EAACFEA91D25CEFD00668199 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - }; - name = Debug; - }; - EAACFEAA1D25CEFD00668199 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - EAACFEAC1D25CEFD00668199 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - HEADER_SEARCH_PATHS = ""; - INFOPLIST_FILE = ManualExample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_BUNDLE_IDENTIFIER = com.posthog.ManualExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - EAACFEAD1D25CEFD00668199 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - HEADER_SEARCH_PATHS = ""; - INFOPLIST_FILE = ManualExample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_BUNDLE_IDENTIFIER = com.posthog.ManualExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - EAACFE8F1D25CEFD00668199 /* Build configuration list for PBXProject "ManualExample" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - EAACFEA91D25CEFD00668199 /* Debug */, - EAACFEAA1D25CEFD00668199 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - EAACFEAB1D25CEFD00668199 /* Build configuration list for PBXNativeTarget "ManualExample" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - EAACFEAC1D25CEFD00668199 /* Debug */, - EAACFEAD1D25CEFD00668199 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = EAACFE8C1D25CEFD00668199 /* Project object */; -} diff --git a/Examples/ManualExample/ManualExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Examples/ManualExample/ManualExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 72aea3868..000000000 --- a/Examples/ManualExample/ManualExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/Examples/ManualExample/ManualExample/AppDelegate.h b/Examples/ManualExample/ManualExample/AppDelegate.h deleted file mode 100644 index 1de344e20..000000000 --- a/Examples/ManualExample/ManualExample/AppDelegate.h +++ /dev/null @@ -1,9 +0,0 @@ -#import - - -@interface AppDelegate : UIResponder - -@property (strong, nonatomic) UIWindow *window; - - -@end diff --git a/Examples/ManualExample/ManualExample/AppDelegate.m b/Examples/ManualExample/ManualExample/AppDelegate.m deleted file mode 100644 index 9f752d712..000000000 --- a/Examples/ManualExample/ManualExample/AppDelegate.m +++ /dev/null @@ -1,50 +0,0 @@ -#import -#import "AppDelegate.h" - - -@interface AppDelegate () - -@end - - -@implementation AppDelegate - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions -{ - // Use your own apiKey! - [PHGPostHog setupWithConfiguration:[PHGPostHogConfiguration configurationWithApiKey:@"_6SG-F7I1vCuZ-HdJL3VZQqjBlaSb1_20hDPwqMNnGI" host:@"https://app.posthog.com"]]; - [[PHGPostHog sharedPostHog] capture:@"Manual Example Launched"]; - [[PHGPostHog sharedPostHog] flush]; - - [[PHGPostHog sharedPostHog] group:@"some-group" groupKey:@"id:4" properties: @{@"company_name":@"Awesome Inc"}]; - return YES; -} - -- (void)applicationWillResignActive:(UIApplication *)application -{ - // 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. - // 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. -} - -- (void)applicationDidEnterBackground:(UIApplication *)application -{ - // 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. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. -} - -- (void)applicationWillEnterForeground:(UIApplication *)application -{ - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. -} - -- (void)applicationDidBecomeActive:(UIApplication *)application -{ - // 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. -} - -- (void)applicationWillTerminate:(UIApplication *)application -{ - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. -} - -@end diff --git a/Examples/ManualExample/ManualExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/Examples/ManualExample/ManualExample/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 118c98f74..000000000 --- a/Examples/ManualExample/ManualExample/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Examples/ManualExample/ManualExample/Base.lproj/LaunchScreen.storyboard b/Examples/ManualExample/ManualExample/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 2e721e183..000000000 --- a/Examples/ManualExample/ManualExample/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Examples/ManualExample/ManualExample/Base.lproj/Main.storyboard b/Examples/ManualExample/ManualExample/Base.lproj/Main.storyboard deleted file mode 100644 index 2c10ea93d..000000000 --- a/Examples/ManualExample/ManualExample/Base.lproj/Main.storyboard +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Examples/ManualExample/ManualExample/Info.plist b/Examples/ManualExample/ManualExample/Info.plist deleted file mode 100644 index 6905cc67b..000000000 --- a/Examples/ManualExample/ManualExample/Info.plist +++ /dev/null @@ -1,40 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/Examples/ManualExample/ManualExample/ViewController.h b/Examples/ManualExample/ManualExample/ViewController.h deleted file mode 100644 index db4e1ecda..000000000 --- a/Examples/ManualExample/ManualExample/ViewController.h +++ /dev/null @@ -1,7 +0,0 @@ -#import - - -@interface ViewController : UIViewController - - -@end diff --git a/Examples/ManualExample/ManualExample/ViewController.m b/Examples/ManualExample/ManualExample/ViewController.m deleted file mode 100644 index 38bec586f..000000000 --- a/Examples/ManualExample/ManualExample/ViewController.m +++ /dev/null @@ -1,33 +0,0 @@ -#import -#import "ViewController.h" - - -@interface ViewController () - -@end - - -@implementation ViewController - -- (void)viewDidLoad -{ - [super viewDidLoad]; - [[PHGPostHog sharedPostHog] capture:@"Manual Example Main View Loaded"]; - [[PHGPostHog sharedPostHog] flush]; - - [[PHGPostHog sharedPostHog] isFeatureEnabled:@"test-flag"]; -} - -- (void)didReceiveMemoryWarning -{ - [super didReceiveMemoryWarning]; - // Dispose of any resources that can be recreated. -} - -- (IBAction)fireEvent:(id)sender -{ - [[PHGPostHog sharedPostHog] capture:@"Manual Example Fire Event"]; - [[PHGPostHog sharedPostHog] flush]; -} - -@end diff --git a/Examples/ManualExample/ManualExample/main.m b/Examples/ManualExample/ManualExample/main.m deleted file mode 100644 index 42efc4fe1..000000000 --- a/Examples/ManualExample/ManualExample/main.m +++ /dev/null @@ -1,10 +0,0 @@ -#import -#import "AppDelegate.h" - -int main(int argc, char *argv[]) -{ - @autoreleasepool - { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -} diff --git a/LICENSE b/LICENSE index 1c8454414..7e799e3c4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,8 +1,6 @@ -The MIT License (MIT) +MIT License -Copyright (c) 2020 PostHog (part of Hiberly Inc) - -Copyright (c) 2016 Segment.io, Inc. +Copyright (c) [2023] [PostHog] Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index 57c894c3c..63f7ddda2 100644 --- a/Makefile +++ b/Makefile @@ -1,49 +1,35 @@ -XC_ARGS := -workspace PostHog.xcworkspace GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES -IOS_XCARGS := $(XC_ARGS) -destination "platform=iOS Simulator,name=iPhone 11" -sdk iphonesimulator -TVOS_XCARGS := $(XC_ARGS) -destination "platform=tvOS Simulator,name=Apple TV" -XC_BUILD_ARGS := -scheme PostHog ONLY_ACTIVE_ARCH=NO -XC_TEST_ARGS := GCC_GENERATE_TEST_COVERAGE_FILES=YES SWIFT_VERSION=4.2 RUN_E2E_TESTS=$(RUN_E2E_TESTS) WEBHOOK_AUTH_USERNAME=$(WEBHOOK_AUTH_USERNAME) +.PHONY: build buildSdk buildExample format swiftLint swiftFormat test testOnSimulator lint bootstrap releaseCocoaPods -bootstrap: - .buildscript/bootstrap.sh - -dependencies: Podfile PostHog.podspec - pod install - -lint: - pod lib lint --allow-warnings - -carthage: - carthage build --no-skip-current +build: buildSdk buildExample -archive: carthage - carthage archive PostHog +buildSdk: + set -o pipefail && xcodebuild build -project PostHog.xcodeproj -scheme PostHog -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.0.1' | xcpretty -clean-ios: - set -o pipefail && xcodebuild $(IOS_XCARGS) -scheme PostHog clean | xcpretty +buildExample: + set -o pipefail && xcodebuild build -project PostHog.xcodeproj -scheme PostHogExample -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.0.1' | xcpretty -clean-tvos: - set -o pipefail && xcodebuild $(TVOS_XCARGS) -scheme PostHog clean | xcpretty +format: swiftLint swiftFormat -clean: clean-ios clean-tvos +swiftLint: + swiftlint --fix -build-ios: - set -o pipefail && xcodebuild $(IOS_XCARGS) $(XC_BUILD_ARGS) | xcpretty +swiftFormat: + swiftformat . --swiftversion 5.3 -build-tvos: - set -o pipefail && xcodebuild $(TVOS_XCARGS) $(XC_BUILD_ARGS) | xcpretty +testOnSimulator: + set -o pipefail && xcodebuild test -project PostHog.xcodeproj -scheme PostHog -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.0.1' | xcpretty -build: build-ios build-tvos +test: + swift test -test-ios: - @set -o pipefail && xcodebuild test $(IOS_XCARGS) -scheme PostHogTests $(XC_TEST_ARGS) | xcpretty --report junit - -test-tvos: - @set -o pipefail && xcodebuild test $(TVOS_XCARGS) -scheme PostHogTestsTVOS $(XC_TEST_ARGS) | xcpretty --report junit - -test: test-ios test-tvos +lint: + swiftformat . --lint --swiftversion 5.3 && swiftlint -xctest: - xctool $(IOS_XCARGS) -scheme PostHogTests $(XC_TEST_ARGS) run-tests -sdk iphonesimulator +# requires gem and brew +bootstrap: + gem install xcpretty + brew install swiftlint + brew install swiftformat -.PHONY: bootstrap dependencies lint carthage archive build test xctest clean +releaseCocoaPods: + pod trunk push PostHog.podspec --allow-warnings diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 000000000..caad2dd4d --- /dev/null +++ b/Package.resolved @@ -0,0 +1,52 @@ +{ + "object": { + "pins": [ + { + "package": "CwlCatchException", + "repositoryURL": "https://github.com/mattgallagher/CwlCatchException.git", + "state": { + "branch": null, + "revision": "3b123999de19bf04905bc1dfdb76f817b0f2cc00", + "version": "2.1.2" + } + }, + { + "package": "CwlPreconditionTesting", + "repositoryURL": "https://github.com/mattgallagher/CwlPreconditionTesting.git", + "state": { + "branch": null, + "revision": "a23ded2c91df9156628a6996ab4f347526f17b6b", + "version": "2.1.2" + } + }, + { + "package": "Nimble", + "repositoryURL": "https://github.com/Quick/Nimble.git", + "state": { + "branch": null, + "revision": "edaedc1ec86f14ac6e2ca495b94f0ff7150d98d0", + "version": "12.3.0" + } + }, + { + "package": "OHHTTPStubs", + "repositoryURL": "https://github.com/AliSoftware/OHHTTPStubs.git", + "state": { + "branch": null, + "revision": "12f19662426d0434d6c330c6974d53e2eb10ecd9", + "version": "9.1.0" + } + }, + { + "package": "Quick", + "repositoryURL": "https://github.com/Quick/Quick.git", + "state": { + "branch": null, + "revision": "16910e406be96e08923918315388c3e989deac9e", + "version": "6.1.0" + } + } + ] + }, + "version": 1 +} diff --git a/Package.swift b/Package.swift index 3940a69e1..7e86d6065 100644 --- a/Package.swift +++ b/Package.swift @@ -1,35 +1,41 @@ // swift-tools-version:5.3 - import PackageDescription let package = Package( name: "PostHog", platforms: [ - .iOS(.v11), .tvOS(.v11) + .macOS(.v10_14), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. .library( name: "PostHog", - targets: ["PostHog"]), + targets: ["PostHog"] + ), + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package(url: "https://github.com/Quick/Quick.git", from: "6.0.0"), + .package(url: "https://github.com/Quick/Nimble.git", from: "12.0.0"), + .package(url: "https://github.com/AliSoftware/OHHTTPStubs.git", from: "9.0.0"), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. - // Targets can depend on other targets in this package, and on products in packages which this package depends on. + // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "PostHog", - dependencies: [], - path: "PostHog/", - exclude: ["Info.plist"], - sources: ["Classes", - "Internal", - "Vendor"], - publicHeadersPath: "Classes", - cSettings: [ - .headerSearchPath("Vendor"), - .headerSearchPath("Internal"), - .headerSearchPath("Classes"), - ] - ) + path: "PostHog" + ), + .testTarget( + name: "PostHogTests", + dependencies: [ + "PostHog", + "Quick", + "Nimble", + "OHHTTPStubs", + .product(name: "OHHTTPStubsSwift", package: "OHHTTPStubs"), + ], + path: "PostHogTests" + ), ] ) diff --git a/Podfile b/Podfile deleted file mode 100644 index d17932167..000000000 --- a/Podfile +++ /dev/null @@ -1,37 +0,0 @@ -# Uncomment the next line to define a global platform for your project -platform :ios, '11.0' - -target 'PostHog' do - # Comment the next line if you don't want to use dynamic frameworks - use_frameworks! - - # Pods for PostHog - - def shared_testing_pods - pod 'Quick', '~> 1.2.0' - pod 'Nimble', '~> 9.2.0' - pod 'Nocilla', '~> 0.11.0' - pod 'Alamofire', '~> 4.5' - pod 'Alamofire-Synchronous', '~> 4.0' - end - - target 'PostHogTests' do - # Pods for testing - shared_testing_pods - end - - target 'PostHogTestsTVOS' do - # Pods for testing - shared_testing_pods - end - - post_install do |installer| - installer.generated_projects.each do |project| - project.targets.each do |target| - target.build_configurations.each do |config| - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0' - end - end - end - end -end diff --git a/Podfile.lock b/Podfile.lock deleted file mode 100644 index b9658ec2d..000000000 --- a/Podfile.lock +++ /dev/null @@ -1,33 +0,0 @@ -PODS: - - Alamofire (4.9.1) - - Alamofire-Synchronous (4.0.0): - - Alamofire (~> 4.0) - - Nimble (9.2.1) - - Nocilla (0.11.0) - - Quick (1.2.0) - -DEPENDENCIES: - - Alamofire (~> 4.5) - - Alamofire-Synchronous (~> 4.0) - - Nimble (~> 9.2.0) - - Nocilla (~> 0.11.0) - - Quick (~> 1.2.0) - -SPEC REPOS: - trunk: - - Alamofire - - Alamofire-Synchronous - - Nimble - - Nocilla - - Quick - -SPEC CHECKSUMS: - Alamofire: 85e8a02c69d6020a0d734f6054870d7ecb75cf18 - Alamofire-Synchronous: eedf1e6e961c3795a63c74990b3f7d9fbfac7e50 - Nimble: e7e615c0335ee4bf5b0d786685451e62746117d5 - Nocilla: 7af7a386071150cc8aa5da4da97d060f049dd61c - Quick: 58d203b1c5e27fff7229c4c1ae445ad7069a7a08 - -PODFILE CHECKSUM: c19e028becd557164e8f03bba870e4dd47272e0a - -COCOAPODS: 1.12.1 diff --git a/PostHog.podspec b/PostHog.podspec index 00537744c..bd7f4acd2 100644 --- a/PostHog.podspec +++ b/PostHog.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "PostHog" - s.version = "2.1.0" + s.version = "3.0.0-alpha.2" s.summary = "The hassle-free way to add posthog to your iOS app." s.description = <<-DESC @@ -10,19 +10,19 @@ Pod::Spec.new do |s| s.homepage = "http://posthog.com/" s.license = { :type => 'MIT' } - s.author = { "PostHog" => "tim@posthog.com" } + s.author = { "PostHog" => "engineering@posthog.com" } s.source = { :git => "https://github.com/PostHog/posthog-ios.git", :tag => s.version.to_s } s.social_media_url = 'https://twitter.com/PostHog' - s.ios.deployment_target = '11.0' - s.tvos.deployment_target = '11.0' + s.ios.deployment_target = '13.0' + s.tvos.deployment_target = '13.0' + s.osx.deployment_target = "10.14" + s.watchos.deployment_target = "6.0" + s.swift_versions = "5.3" - s.ios.frameworks = 'CoreTelephony' - s.frameworks = 'Security', 'StoreKit', 'SystemConfiguration', 'UIKit' + s.frameworks = 'Foundation' s.source_files = [ - 'PostHog/Classes/**/*', - 'PostHog/Internal/**/*', - 'PostHog/Vendor/**/*' + 'PostHog/**/*' ] end diff --git a/PostHog.xcodeproj/project.pbxproj b/PostHog.xcodeproj/project.pbxproj index ccbb1e53f..f2b695479 100644 --- a/PostHog.xcodeproj/project.pbxproj +++ b/PostHog.xcodeproj/project.pbxproj @@ -3,854 +3,861 @@ archiveVersion = 1; classes = { }; - objectVersion = 55; + objectVersion = 56; objects = { /* Begin PBXBuildFile section */ - 2909CFFA247568CF00977728 /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29BC6B32244DA78E00D7C963 /* CoreTelephony.framework */; platformFilters = (ios, maccatalyst, ); }; - 3A3AE0B42A29C63800556065 /* PHGApplicationUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A3AE0B22A29C63800556065 /* PHGApplicationUtils.h */; }; - 3A3AE0B52A29C63800556065 /* PHGApplicationUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A3AE0B32A29C63800556065 /* PHGApplicationUtils.m */; }; - 4CD0E7D07BBBB59FF4827356 /* Pods_PostHog_PostHogTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B1EB21C600E7907C3479131 /* Pods_PostHog_PostHogTests.framework */; }; - 6EEC1C712017EA370089C478 /* EndToEndTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EEC1C702017EA370089C478 /* EndToEndTests.swift */; }; - 9D8CE59023EE014E00197D0C /* CryptoTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EADEB8E91DECD335005322DA /* CryptoTest.swift */; }; - 9D8CE59123EE014E00197D0C /* UserDefaultsStorageTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EADEB8E61DECD335005322DA /* UserDefaultsStorageTest.swift */; }; - 9D8CE59223EE014E00197D0C /* CapturingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA542761EB4035400945DA7 /* CapturingTests.swift */; }; - 9D8CE59323EE014E00197D0C /* ContextTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EADEB8E81DECD335005322DA /* ContextTest.swift */; }; - 9D8CE59423EE014E00197D0C /* SerializationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A31958EE2385AC3A00A47EFA /* SerializationTests.m */; }; - 9D8CE59523EE014E00197D0C /* HTTPClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EADEB8E01DECD335005322DA /* HTTPClientTest.swift */; }; - 9D8CE59623EE014E00197D0C /* PostHogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EADEB8E71DECD335005322DA /* PostHogTests.swift */; }; - 9D8CE59723EE014E00197D0C /* FileStorageTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EADEB8E51DECD335005322DA /* FileStorageTest.swift */; }; - 9D8CE59823EE014E00197D0C /* PostHogUtilTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F61EB4AE1F996DEF0038C8C0 /* PostHogUtilTests.swift */; }; - 9D8CE59923EE014E00197D0C /* StoreKitCapturerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA5427F1EB4382100945DA7 /* StoreKitCapturerTests.swift */; }; - 9D8CE59A23EE014E00197D0C /* MiddlewareTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA8F09731E24C5C600B8B93F /* MiddlewareTests.swift */; }; - 9D8CE59C23EE014E00197D0C /* NSData+PHGGUNZIPP.m in Sources */ = {isa = PBXBuildFile; fileRef = EADEB8E31DECD335005322DA /* NSData+PHGGUNZIPP.m */; }; - 9D8CE59D23EE014E00197D0C /* EndToEndTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EEC1C702017EA370089C478 /* EndToEndTests.swift */; }; - 9D8CE59E23EE014E00197D0C /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = EADEB8E41DECD335005322DA /* TestUtils.swift */; }; - 9D8CE5A023EE014E00197D0C /* PostHog.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EADEB85B1DECD080005322DA /* PostHog.framework */; }; - A31958EF2385AC3A00A47EFA /* SerializationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A31958EE2385AC3A00A47EFA /* SerializationTests.m */; }; - BF8C806E9390DA2FC00D1812 /* PHGPayloadManager.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8C8E6F09398C169F35E675 /* PHGPayloadManager.h */; settings = {ATTRIBUTES = (Private, ); }; }; - BF8C814E5A3BA7A84540D733 /* PHGContext.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8C8BD43F67239FA1902206 /* PHGContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BF8C817D68B4E9B441E21D64 /* UIViewController+PHGScreen.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8C8AC249BECDAF13435FD2 /* UIViewController+PHGScreen.h */; settings = {ATTRIBUTES = (Private, ); }; }; - BF8C8181F96E1C8CB86D783D /* PHGPostHogUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8C8C0E43DD950DB9CA7725 /* PHGPostHogUtils.h */; settings = {ATTRIBUTES = (Private, ); }; }; - BF8C826724E6F30D02E53EA6 /* ObjC.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8C88E533159E768C65C355 /* ObjC.h */; settings = {ATTRIBUTES = (Private, ); }; }; - BF8C8331F9BAC4B30CAAC061 /* PHGIdentifyPayload.m in Sources */ = {isa = PBXBuildFile; fileRef = BF8C811B41CBE38598366297 /* PHGIdentifyPayload.m */; }; - BF8C8334DB2564A7CFA7A87A /* PHGPayloadManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BF8C8CD474E1A05E0D0F8C88 /* PHGPayloadManager.m */; }; - BF8C8374E0D8F97469715282 /* PHGPayload.m in Sources */ = {isa = PBXBuildFile; fileRef = BF8C80633A9C834A27298FDE /* PHGPayload.m */; }; - BF8C838C46F024993F46C108 /* PHGAliasPayload.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8C8558DAB439462566DA94 /* PHGAliasPayload.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BF8C84098D3C47C6F7B7FF9A /* PHGHTTPClient.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8C8A9AD350CD86D85DF182 /* PHGHTTPClient.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BF8C845A65AB5AD06E1EE26B /* PHGStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8C83BC7E9D748CA963E2C1 /* PHGStorage.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BF8C84CDF20A0959FC24E076 /* PHGPostHogUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = BF8C8CD3AE863FA75ADF0189 /* PHGPostHogUtils.m */; }; - BF8C84DEF5634524F1F9110A /* PHGUserDefaultsStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8C8E50F0C96453793BC955 /* PHGUserDefaultsStorage.h */; settings = {ATTRIBUTES = (Private, ); }; }; - BF8C850E6C20F8E009469D1E /* PHGPostHogIntegration.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8C811FC65903D9C4661465 /* PHGPostHogIntegration.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BF8C856B25377945554598DF /* UIViewController+PHGScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = BF8C8D864F905E2A4AF297E8 /* UIViewController+PHGScreen.m */; }; - BF8C86649922EFA5F66C5BE7 /* PHGIdentifyPayload.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8C8FC3DCC9BEBB4474F373 /* PHGIdentifyPayload.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BF8C86ED787477703D8BCD6C /* PHGScreenPayload.m in Sources */ = {isa = PBXBuildFile; fileRef = BF8C83FDEE5C86782CB11D8B /* PHGScreenPayload.m */; }; - BF8C8735F5CF80C193F5AEDF /* PHGAliasPayload.m in Sources */ = {isa = PBXBuildFile; fileRef = BF8C8BC32FD2DF9975AB3A35 /* PHGAliasPayload.m */; }; - BF8C8791A47E4BF8586F0B43 /* PHGCapturePayload.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8C8EE82249B851E06328EB /* PHGCapturePayload.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BF8C87A812BB60E58DB7B1E4 /* PHGScreenPayload.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8C82BFA5872F7E8CBFA682 /* PHGScreenPayload.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BF8C87E4B70CA478721EB11D /* PHGCapturePayload.m in Sources */ = {isa = PBXBuildFile; fileRef = BF8C8D451AA492DA225B7DC7 /* PHGCapturePayload.m */; }; - BF8C87E7C76982625BA92F48 /* PHGAES256Crypto.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8C81224E7FCD6D6BDF4614 /* PHGAES256Crypto.h */; settings = {ATTRIBUTES = (Private, ); }; }; - BF8C8862C12DE007F3494399 /* PHGContext.m in Sources */ = {isa = PBXBuildFile; fileRef = BF8C8F6DD6E7763114B6FCD1 /* PHGContext.m */; }; - BF8C88B5A93AE29953A01E65 /* PHGMiddleware.m in Sources */ = {isa = PBXBuildFile; fileRef = BF8C8AAA842A95EC8B4ED380 /* PHGMiddleware.m */; }; - BF8C890FADEC88022E7A3588 /* NSData+PHGGZIP.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8C8F47D9775221FE48AA4D /* NSData+PHGGZIP.h */; settings = {ATTRIBUTES = (Private, ); }; }; - BF8C8A634BCECED539A18D57 /* PHGPostHogIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = BF8C8AF3C1D249548CF69443 /* PHGPostHogIntegration.m */; }; - BF8C8AD1FBFBB6941DE29D3B /* PHGFileStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8C86671862A5FFA20C721C /* PHGFileStorage.h */; settings = {ATTRIBUTES = (Private, ); }; }; - BF8C8AEC72DA925C8B65B1A8 /* PHGMiddleware.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8C81A4093856C696EF943D /* PHGMiddleware.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BF8C8AF845410CCF3744EEA1 /* PHGAES256Crypto.m in Sources */ = {isa = PBXBuildFile; fileRef = BF8C861A6C2B0C33AC1E7D34 /* PHGAES256Crypto.m */; }; - BF8C8C9C0FD46725EA63890A /* ObjC.m in Sources */ = {isa = PBXBuildFile; fileRef = BF8C8388F05135177F79C286 /* ObjC.m */; }; - BF8C8CAB52120583B9BB4934 /* PHGPayload.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8C8E2E6C04BE91346F441D /* PHGPayload.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BF8C8D75F1E684EA93578DF5 /* NSData+PHGGZIP.m in Sources */ = {isa = PBXBuildFile; fileRef = BF8C802EB41E4E93050D28BA /* NSData+PHGGZIP.m */; }; - BF8C8D8EE891F5599F3D78D7 /* PHGCrypto.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8C89BCDF12D1B64E9475F1 /* PHGCrypto.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BF8C8D9B4A6389088569438F /* PHGHTTPClient.m in Sources */ = {isa = PBXBuildFile; fileRef = BF8C8BFC78C6AE9EB9732E26 /* PHGHTTPClient.m */; }; - BF8C8DC55C0552CCAFE7BEBF /* PHGMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8C8EC5CFC52D2FDC180500 /* PHGMacros.h */; settings = {ATTRIBUTES = (Private, ); }; }; - BF8C8DCC18E3AA39CCB330BC /* PHGStoreKitCapturer.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8C8CBED16F21362625BE46 /* PHGStoreKitCapturer.h */; settings = {ATTRIBUTES = (Private, ); }; }; - BF8C8E28834B124F2E9835A5 /* PHGUserDefaultsStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = BF8C8CFB8B293A63E897C5A8 /* PHGUserDefaultsStorage.m */; }; - BF8C8E57BD722DB6AEFF6835 /* PHGFileStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = BF8C8EB90563BC3956BA25E4 /* PHGFileStorage.m */; }; - BF8C8EF6349359A33E8C6DFD /* PHGUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = BF8C8EF9E0ABC31A6E3C0868 /* PHGUtils.m */; }; - BF8C8F2782C85B046B01C044 /* PHGIntegration.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8C8DC90BD0C5D0AF19498F /* PHGIntegration.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BF8C8FC2CB4FAE70F5879308 /* PHGStoreKitCapturer.m in Sources */ = {isa = PBXBuildFile; fileRef = BF8C8233411FABE41CC0964B /* PHGStoreKitCapturer.m */; }; - BF8C8FCEC80B47BF2236236E /* PHGUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8C81ED70A463EA7799B9B6 /* PHGUtils.h */; settings = {ATTRIBUTES = (Private, ); }; }; - DF146944722A2847072DB568 /* Pods_PostHog_PostHogTestsTVOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 73B8456977204A7F09DC6374 /* Pods_PostHog_PostHogTestsTVOS.framework */; }; - E1D8958848D3141E00E771F3 /* Pods_PostHog.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C006A95003B78D95FBA5AB2 /* Pods_PostHog.framework */; }; - EA88A5981DED7608009FB66A /* PHGSerializableValue.h in Headers */ = {isa = PBXBuildFile; fileRef = EA88A5971DED7608009FB66A /* PHGSerializableValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EA8F09741E24C5C600B8B93F /* MiddlewareTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA8F09731E24C5C600B8B93F /* MiddlewareTests.swift */; }; - EAA542771EB4035400945DA7 /* CapturingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA542761EB4035400945DA7 /* CapturingTests.swift */; }; - EAA5427D1EB42B8C00945DA7 /* PHGReachability.h in Headers */ = {isa = PBXBuildFile; fileRef = EAA5427B1EB42B8C00945DA7 /* PHGReachability.h */; settings = {ATTRIBUTES = (Private, ); }; }; - EAA5427E1EB42B8C00945DA7 /* PHGReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = EAA5427C1EB42B8C00945DA7 /* PHGReachability.m */; }; - EAA542801EB4382100945DA7 /* StoreKitCapturerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA5427F1EB4382100945DA7 /* StoreKitCapturerTests.swift */; }; - EADEB8601DECD080005322DA /* PostHog.h in Headers */ = {isa = PBXBuildFile; fileRef = EADEB85E1DECD080005322DA /* PostHog.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EADEB86F1DECD0EF005322DA /* PostHog.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EADEB85B1DECD080005322DA /* PostHog.framework */; }; - EADEB8DC1DECD12B005322DA /* PHGPostHog.h in Headers */ = {isa = PBXBuildFile; fileRef = EADEB8A91DECD12B005322DA /* PHGPostHog.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EADEB8DD1DECD12B005322DA /* PHGPostHog.m in Sources */ = {isa = PBXBuildFile; fileRef = EADEB8AA1DECD12B005322DA /* PHGPostHog.m */; }; - EADEB8DE1DECD12B005322DA /* PHGPostHogConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = EADEB8AB1DECD12B005322DA /* PHGPostHogConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EADEB8DF1DECD12B005322DA /* PHGPostHogConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = EADEB8AC1DECD12B005322DA /* PHGPostHogConfiguration.m */; }; - EADEB8EE1DECD335005322DA /* HTTPClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EADEB8E01DECD335005322DA /* HTTPClientTest.swift */; }; - EADEB8EF1DECD335005322DA /* NSData+PHGGUNZIPP.m in Sources */ = {isa = PBXBuildFile; fileRef = EADEB8E31DECD335005322DA /* NSData+PHGGUNZIPP.m */; }; - EADEB8F01DECD335005322DA /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = EADEB8E41DECD335005322DA /* TestUtils.swift */; }; - EADEB8F11DECD335005322DA /* FileStorageTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EADEB8E51DECD335005322DA /* FileStorageTest.swift */; }; - EADEB8F21DECD335005322DA /* UserDefaultsStorageTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EADEB8E61DECD335005322DA /* UserDefaultsStorageTest.swift */; }; - EADEB8F31DECD335005322DA /* PostHogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EADEB8E71DECD335005322DA /* PostHogTests.swift */; }; - EADEB8F41DECD335005322DA /* ContextTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EADEB8E81DECD335005322DA /* ContextTest.swift */; }; - EADEB8F51DECD335005322DA /* CryptoTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EADEB8E91DECD335005322DA /* CryptoTest.swift */; }; - F2A54FB028772F97006C53FF /* FeatureFlagTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2A54FAF28772F97006C53FF /* FeatureFlagTests.swift */; }; - F2A54FB128772F97006C53FF /* FeatureFlagTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2A54FAF28772F97006C53FF /* FeatureFlagTests.swift */; }; - F2A54FB32877582E006C53FF /* PHGGroupPayload.m in Sources */ = {isa = PBXBuildFile; fileRef = F2A54FB22877582E006C53FF /* PHGGroupPayload.m */; }; - F2A54FB52877583C006C53FF /* PHGGroupPayload.h in Headers */ = {isa = PBXBuildFile; fileRef = F2A54FB42877583C006C53FF /* PHGGroupPayload.h */; settings = {ATTRIBUTES = (Public, ); }; }; - F61EB4AF1F996DEF0038C8C0 /* PostHogUtilTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F61EB4AE1F996DEF0038C8C0 /* PostHogUtilTests.swift */; }; + 3A0F108329C47940002C0084 /* UIViewExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A0F108229C47940002C0084 /* UIViewExample.swift */; }; + 3A0F108529C9ABB6002C0084 /* ReadWriteLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A0F108429C9ABB6002C0084 /* ReadWriteLock.swift */; }; + 3A0F108929C9BD76002C0084 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A0F108829C9BD76002C0084 /* Errors.swift */; }; + 3A580B4329E489D000C5C6F3 /* URLSession+body.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A580B4229E489D000C5C6F3 /* URLSession+body.swift */; }; + 3A62646A29C9E385007E8C07 /* MockPostHogServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A62646929C9E385007E8C07 /* MockPostHogServer.swift */; }; + 3A62647129CAF67B007E8C07 /* PostHogSessionManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A62647029CAF67B007E8C07 /* PostHogSessionManagerTest.swift */; }; + 3A62647529CB0168007E8C07 /* TestPostHog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A62647429CB0168007E8C07 /* TestPostHog.swift */; }; + 3A81BEAE297704F400A6908D /* PostHog.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AC745B5296D6FE60025C109 /* PostHog.framework */; }; + 3A81BEAF297704F400A6908D /* PostHog.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3AC745B5296D6FE60025C109 /* PostHog.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 3AA34CFA296D951A003398F4 /* PostHogExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA34CF9296D951A003398F4 /* PostHogExampleApp.swift */; }; + 3AA34CFC296D951A003398F4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA34CFB296D951A003398F4 /* ContentView.swift */; }; + 3AA34CFE296D951B003398F4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3AA34CFD296D951B003398F4 /* Assets.xcassets */; }; + 3AA34D01296D951B003398F4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3AA34D00296D951B003398F4 /* Preview Assets.xcassets */; }; + 3AA34D17296D9993003398F4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA34D16296D9993003398F4 /* AppDelegate.swift */; }; + 3AC745C0296D6FE60025C109 /* PostHog.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AC745B5296D6FE60025C109 /* PostHog.framework */; }; + 3AC745C6296D6FE60025C109 /* PostHog.h in Headers */ = {isa = PBXBuildFile; fileRef = 3AC745B8296D6FE60025C109 /* PostHog.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3AE3FB2C2991320300AFFC18 /* Api.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE3FB2B2991320300AFFC18 /* Api.swift */; }; + 3AE3FB332991388500AFFC18 /* PostHogQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE3FB322991388500AFFC18 /* PostHogQueue.swift */; }; + 3AE3FB37299162EA00AFFC18 /* PostHogApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE3FB36299162EA00AFFC18 /* PostHogApi.swift */; }; + 3AE3FB3D29924E8200AFFC18 /* PostHogSDK.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE3FB3C29924E8200AFFC18 /* PostHogSDK.swift */; }; + 3AE3FB3F29924F4F00AFFC18 /* PostHogConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE3FB3E29924F4F00AFFC18 /* PostHogConfig.swift */; }; + 3AE3FB432992985A00AFFC18 /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE3FB422992985A00AFFC18 /* Reachability.swift */; }; + 3AE3FB472992AB0000AFFC18 /* Hedgelog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE3FB462992AB0000AFFC18 /* Hedgelog.swift */; }; + 3AE3FB49299391DF00AFFC18 /* PostHogStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE3FB48299391DF00AFFC18 /* PostHogStorage.swift */; }; + 3AE3FB4B2993A68500AFFC18 /* PostHogStorageTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE3FB4A2993A68500AFFC18 /* PostHogStorageTest.swift */; }; + 3AE3FB4E2993D1D600AFFC18 /* PostHogSessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE3FB4D2993D1D600AFFC18 /* PostHogSessionManager.swift */; }; + 690FF05F2AE7E2D400A0B06B /* Data+Gzip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 690FF05E2AE7E2D400A0B06B /* Data+Gzip.swift */; }; + 690FF0AF2AEB9C1400A0B06B /* DateUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 690FF0AE2AEB9C1400A0B06B /* DateUtils.swift */; }; + 690FF0B52AEBBD3C00A0B06B /* DictUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 690FF0B42AEBBD3C00A0B06B /* DictUtils.swift */; }; + 690FF0BB2AEF8B8200A0B06B /* PostHogContextTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 690FF0BA2AEF8B8200A0B06B /* PostHogContextTest.swift */; }; + 690FF0BD2AEF93F400A0B06B /* PostHogFeatureFlagsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 690FF0BC2AEF93F400A0B06B /* PostHogFeatureFlagsTest.swift */; }; + 690FF0BF2AEFA97F00A0B06B /* FileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 690FF0BE2AEFA97F00A0B06B /* FileUtils.swift */; }; + 690FF0C52AEFAE8200A0B06B /* PostHogLegacyQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 690FF0C42AEFAE8200A0B06B /* PostHogLegacyQueue.swift */; }; + 690FF0DF2AEFBC5700A0B06B /* PostHogLegacyQueueTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 690FF0DE2AEFBC5700A0B06B /* PostHogLegacyQueueTest.swift */; }; + 690FF0E12AEFC59100A0B06B /* PostHogFileBackedQueueTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 690FF0E02AEFC59100A0B06B /* PostHogFileBackedQueueTest.swift */; }; + 690FF0E32AEFD12900A0B06B /* PostHogConfigTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 690FF0E22AEFD12900A0B06B /* PostHogConfigTest.swift */; }; + 690FF0E92AEFD3BD00A0B06B /* PostHogQueueTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 690FF0E82AEFD3BD00A0B06B /* PostHogQueueTest.swift */; }; + 690FF0EB2AEFF22F00A0B06B /* Quick in Frameworks */ = {isa = PBXBuildFile; productRef = 690FF0EA2AEFF22F00A0B06B /* Quick */; }; + 690FF0ED2AEFF23300A0B06B /* Nimble in Frameworks */ = {isa = PBXBuildFile; productRef = 690FF0EC2AEFF23300A0B06B /* Nimble */; }; + 690FF0EF2AEFF23D00A0B06B /* OHHTTPStubsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 690FF0EE2AEFF23D00A0B06B /* OHHTTPStubsSwift */; }; + 690FF0F12AEFF24200A0B06B /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = 690FF0F02AEFF24200A0B06B /* OHHTTPStubs */; }; + 690FF0F52AF0F06100A0B06B /* PostHogSDKTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 690FF0F42AF0F06100A0B06B /* PostHogSDKTest.swift */; }; + 69261D132AD5685B00232EC7 /* PostHogFeatureFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69261D122AD5685B00232EC7 /* PostHogFeatureFlags.swift */; }; + 69261D192AD9673500232EC7 /* PostHogBatchUploadInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69261D182AD9673500232EC7 /* PostHogBatchUploadInfo.swift */; }; + 69261D1B2AD9678C00232EC7 /* PostHogEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69261D1A2AD9678C00232EC7 /* PostHogEvent.swift */; }; + 69261D1D2AD967CD00232EC7 /* PostHogFileBackedQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69261D1C2AD967CD00232EC7 /* PostHogFileBackedQueue.swift */; }; + 69261D1F2AD9681300232EC7 /* PostHogConsumerPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69261D1E2AD9681300232EC7 /* PostHogConsumerPayload.swift */; }; + 69261D232AD9784200232EC7 /* PostHogVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69261D222AD9784200232EC7 /* PostHogVersion.swift */; }; + 69261D252AD9787A00232EC7 /* PostHogExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69261D242AD9787A00232EC7 /* PostHogExtensions.swift */; }; + 6926DA8E2ADD2876005760D2 /* PostHogContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6926DA8D2ADD2876005760D2 /* PostHogContext.swift */; }; + 69278D362AE6BC7100BB541A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 69278D352AE6BC7100BB541A /* AppDelegate.m */; }; + 69278D392AE6BC7100BB541A /* SceneDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 69278D382AE6BC7100BB541A /* SceneDelegate.m */; }; + 69278D3C2AE6BC7100BB541A /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 69278D3B2AE6BC7100BB541A /* ViewController.m */; }; + 69278D3F2AE6BC7100BB541A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 69278D3D2AE6BC7100BB541A /* Main.storyboard */; }; + 69278D412AE6BC7200BB541A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69278D402AE6BC7200BB541A /* Assets.xcassets */; }; + 69278D442AE6BC7200BB541A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 69278D422AE6BC7200BB541A /* LaunchScreen.storyboard */; }; + 69278D472AE6BC7200BB541A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 69278D462AE6BC7200BB541A /* main.m */; }; + 69278D4B2AE6BC9000BB541A /* PostHog.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AC745B5296D6FE60025C109 /* PostHog.framework */; }; + 69278D4C2AE6BC9000BB541A /* PostHog.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3AC745B5296D6FE60025C109 /* PostHog.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 69779BEC2AE68E6900D7A48E /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69779BEB2AE68E6900D7A48E /* UIViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 9D8CE58D23EE014E00197D0C /* PBXContainerItemProxy */ = { + 3A81BEB0297704F400A6908D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = EADEB8521DECD080005322DA /* Project object */; + containerPortal = 3AC745AC296D6FE60025C109 /* Project object */; proxyType = 1; - remoteGlobalIDString = EADEB85A1DECD080005322DA; + remoteGlobalIDString = 3AC745B4296D6FE60025C109; remoteInfo = PostHog; }; - EADEB8701DECD0EF005322DA /* PBXContainerItemProxy */ = { + 3AA34D05296D9523003398F4 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = EADEB8521DECD080005322DA /* Project object */; + containerPortal = 3AC745AC296D6FE60025C109 /* Project object */; proxyType = 1; - remoteGlobalIDString = EADEB85A1DECD080005322DA; + remoteGlobalIDString = 3AA34CF6296D951A003398F4; + remoteInfo = PostHogExample; + }; + 3AC745C1296D6FE60025C109 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 3AC745AC296D6FE60025C109 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 3AC745B4296D6FE60025C109; + remoteInfo = PostHog; + }; + 690FF0332AE7C5BB00A0B06B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 690FF02F2AE7C5BA00A0B06B /* PostHogExampleWithPods.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 690FF01E2AE7C5B900A0B06B; + remoteInfo = PostHogExampleWithPods; + }; + 690FF0572AE7DB3700A0B06B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 690FF0532AE7DB3700A0B06B /* PostHogExampleWithSPM.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 690FF0422AE7DB3600A0B06B; + remoteInfo = PostHogExampleWithSPM; + }; + 690FF1782AF3CE8B00A0B06B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 690FF1732AF3CE8A00A0B06B /* PostHogExampleWatchOS.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 690FF1562AF3CE8900A0B06B; + remoteInfo = PostHogExampleWatchOS; + }; + 690FF17A2AF3CE8B00A0B06B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 690FF1732AF3CE8A00A0B06B /* PostHogExampleWatchOS.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 690FF15C2AF3CE8900A0B06B; + remoteInfo = "PostHogExampleWatchOS Watch App"; + }; + 69278D4D2AE6BC9000BB541A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 3AC745AC296D6FE60025C109 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 3AC745B4296D6FE60025C109; remoteInfo = PostHog; }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + 3A81BEB2297704F400A6908D /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 3A81BEAF297704F400A6908D /* PostHog.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 69278D4F2AE6BC9000BB541A /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 69278D4C2AE6BC9000BB541A /* PostHog.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ - 12D0B9751E2B4493AC98D46C /* Pods-PostHog.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PostHog.debug.xcconfig"; path = "Target Support Files/Pods-PostHog/Pods-PostHog.debug.xcconfig"; sourceTree = ""; }; - 13F1CD8F13E34252559F5A83 /* Pods-PostHog-PostHogTestsTVOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PostHog-PostHogTestsTVOS.release.xcconfig"; path = "Target Support Files/Pods-PostHog-PostHogTestsTVOS/Pods-PostHog-PostHogTestsTVOS.release.xcconfig"; sourceTree = ""; }; - 27F9199C916C04653B965505 /* Pods-PostHog-PostHogTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PostHog-PostHogTests.release.xcconfig"; path = "Target Support Files/Pods-PostHog-PostHogTests/Pods-PostHog-PostHogTests.release.xcconfig"; sourceTree = ""; }; - 29BC6B32244DA78E00D7C963 /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/CoreTelephony.framework; sourceTree = DEVELOPER_DIR; }; - 3A3AE0B22A29C63800556065 /* PHGApplicationUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PHGApplicationUtils.h; path = Internal/PHGApplicationUtils.h; sourceTree = ""; }; - 3A3AE0B32A29C63800556065 /* PHGApplicationUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PHGApplicationUtils.m; path = Internal/PHGApplicationUtils.m; sourceTree = ""; }; - 3B1EB21C600E7907C3479131 /* Pods_PostHog_PostHogTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PostHog_PostHogTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 5C006A95003B78D95FBA5AB2 /* Pods_PostHog.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PostHog.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 6EEC1C702017EA370089C478 /* EndToEndTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndToEndTests.swift; sourceTree = ""; }; - 73B8456977204A7F09DC6374 /* Pods_PostHog_PostHogTestsTVOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PostHog_PostHogTestsTVOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 9D8CE5A723EE014E00197D0C /* PostHogTestsTVOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PostHogTestsTVOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - A31958EE2385AC3A00A47EFA /* SerializationTests.m */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.objc; path = SerializationTests.m; sourceTree = ""; tabWidth = 4; }; - BF8C802EB41E4E93050D28BA /* NSData+PHGGZIP.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSData+PHGGZIP.m"; path = "Internal/NSData+PHGGZIP.m"; sourceTree = ""; }; - BF8C80633A9C834A27298FDE /* PHGPayload.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PHGPayload.m; sourceTree = ""; }; - BF8C811B41CBE38598366297 /* PHGIdentifyPayload.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PHGIdentifyPayload.m; sourceTree = ""; }; - BF8C811FC65903D9C4661465 /* PHGPostHogIntegration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PHGPostHogIntegration.h; path = Internal/PHGPostHogIntegration.h; sourceTree = ""; }; - BF8C81224E7FCD6D6BDF4614 /* PHGAES256Crypto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PHGAES256Crypto.h; path = Internal/PHGAES256Crypto.h; sourceTree = ""; }; - BF8C81A4093856C696EF943D /* PHGMiddleware.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PHGMiddleware.h; sourceTree = ""; }; - BF8C81ED70A463EA7799B9B6 /* PHGUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PHGUtils.h; path = Internal/PHGUtils.h; sourceTree = ""; }; - BF8C8233411FABE41CC0964B /* PHGStoreKitCapturer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PHGStoreKitCapturer.m; path = Internal/PHGStoreKitCapturer.m; sourceTree = ""; }; - BF8C82BFA5872F7E8CBFA682 /* PHGScreenPayload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PHGScreenPayload.h; sourceTree = ""; }; - BF8C8388F05135177F79C286 /* ObjC.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ObjC.m; sourceTree = ""; }; - BF8C83BC7E9D748CA963E2C1 /* PHGStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PHGStorage.h; path = Internal/PHGStorage.h; sourceTree = ""; }; - BF8C83FDEE5C86782CB11D8B /* PHGScreenPayload.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PHGScreenPayload.m; sourceTree = ""; }; - BF8C8558DAB439462566DA94 /* PHGAliasPayload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PHGAliasPayload.h; sourceTree = ""; }; - BF8C861A6C2B0C33AC1E7D34 /* PHGAES256Crypto.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PHGAES256Crypto.m; path = Internal/PHGAES256Crypto.m; sourceTree = ""; }; - BF8C86671862A5FFA20C721C /* PHGFileStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PHGFileStorage.h; path = Internal/PHGFileStorage.h; sourceTree = ""; }; - BF8C88E533159E768C65C355 /* ObjC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjC.h; sourceTree = ""; }; - BF8C89860ED4391E417B39E9 /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; - BF8C89BCDF12D1B64E9475F1 /* PHGCrypto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PHGCrypto.h; sourceTree = ""; }; - BF8C8A9AD350CD86D85DF182 /* PHGHTTPClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PHGHTTPClient.h; path = Internal/PHGHTTPClient.h; sourceTree = ""; }; - BF8C8AAA842A95EC8B4ED380 /* PHGMiddleware.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PHGMiddleware.m; sourceTree = ""; }; - BF8C8AC249BECDAF13435FD2 /* UIViewController+PHGScreen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIViewController+PHGScreen.h"; path = "Internal/UIViewController+PHGScreen.h"; sourceTree = ""; }; - BF8C8AF3C1D249548CF69443 /* PHGPostHogIntegration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PHGPostHogIntegration.m; path = Internal/PHGPostHogIntegration.m; sourceTree = ""; }; - BF8C8BC32FD2DF9975AB3A35 /* PHGAliasPayload.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PHGAliasPayload.m; sourceTree = ""; }; - BF8C8BD43F67239FA1902206 /* PHGContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PHGContext.h; sourceTree = ""; }; - BF8C8BFC78C6AE9EB9732E26 /* PHGHTTPClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PHGHTTPClient.m; path = Internal/PHGHTTPClient.m; sourceTree = ""; }; - BF8C8C0E43DD950DB9CA7725 /* PHGPostHogUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PHGPostHogUtils.h; path = Internal/PHGPostHogUtils.h; sourceTree = ""; }; - BF8C8CBED16F21362625BE46 /* PHGStoreKitCapturer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PHGStoreKitCapturer.h; path = Internal/PHGStoreKitCapturer.h; sourceTree = ""; }; - BF8C8CD3AE863FA75ADF0189 /* PHGPostHogUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PHGPostHogUtils.m; path = Internal/PHGPostHogUtils.m; sourceTree = ""; }; - BF8C8CD474E1A05E0D0F8C88 /* PHGPayloadManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PHGPayloadManager.m; sourceTree = ""; }; - BF8C8CFB8B293A63E897C5A8 /* PHGUserDefaultsStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PHGUserDefaultsStorage.m; path = Internal/PHGUserDefaultsStorage.m; sourceTree = ""; }; - BF8C8D451AA492DA225B7DC7 /* PHGCapturePayload.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PHGCapturePayload.m; sourceTree = ""; }; - BF8C8D864F905E2A4AF297E8 /* UIViewController+PHGScreen.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIViewController+PHGScreen.m"; path = "Internal/UIViewController+PHGScreen.m"; sourceTree = ""; }; - BF8C8DC90BD0C5D0AF19498F /* PHGIntegration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PHGIntegration.h; sourceTree = ""; }; - BF8C8E2E6C04BE91346F441D /* PHGPayload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PHGPayload.h; sourceTree = ""; }; - BF8C8E50F0C96453793BC955 /* PHGUserDefaultsStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PHGUserDefaultsStorage.h; path = Internal/PHGUserDefaultsStorage.h; sourceTree = ""; }; - BF8C8E6F09398C169F35E675 /* PHGPayloadManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PHGPayloadManager.h; sourceTree = ""; }; - BF8C8EB90563BC3956BA25E4 /* PHGFileStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PHGFileStorage.m; path = Internal/PHGFileStorage.m; sourceTree = ""; }; - BF8C8EC5CFC52D2FDC180500 /* PHGMacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PHGMacros.h; path = Internal/PHGMacros.h; sourceTree = ""; }; - BF8C8EE82249B851E06328EB /* PHGCapturePayload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PHGCapturePayload.h; sourceTree = ""; }; - BF8C8EF9E0ABC31A6E3C0868 /* PHGUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PHGUtils.m; path = Internal/PHGUtils.m; sourceTree = ""; }; - BF8C8F47D9775221FE48AA4D /* NSData+PHGGZIP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSData+PHGGZIP.h"; path = "Internal/NSData+PHGGZIP.h"; sourceTree = ""; }; - BF8C8F6DD6E7763114B6FCD1 /* PHGContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PHGContext.m; sourceTree = ""; }; - BF8C8FC3DCC9BEBB4474F373 /* PHGIdentifyPayload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PHGIdentifyPayload.h; sourceTree = ""; }; - CCEFC40BEE77A69F2C598C9D /* Pods-PostHog-PostHogTestsTVOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PostHog-PostHogTestsTVOS.debug.xcconfig"; path = "Target Support Files/Pods-PostHog-PostHogTestsTVOS/Pods-PostHog-PostHogTestsTVOS.debug.xcconfig"; sourceTree = ""; }; - D5D6B45A7B89238D0787DFF6 /* Pods-PostHog-PostHogTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PostHog-PostHogTests.debug.xcconfig"; path = "Target Support Files/Pods-PostHog-PostHogTests/Pods-PostHog-PostHogTests.debug.xcconfig"; sourceTree = ""; }; - EA88A5971DED7608009FB66A /* PHGSerializableValue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PHGSerializableValue.h; sourceTree = ""; }; - EA8F09731E24C5C600B8B93F /* MiddlewareTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MiddlewareTests.swift; sourceTree = ""; }; - EAA542761EB4035400945DA7 /* CapturingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CapturingTests.swift; sourceTree = ""; }; - EAA5427B1EB42B8C00945DA7 /* PHGReachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PHGReachability.h; sourceTree = ""; }; - EAA5427C1EB42B8C00945DA7 /* PHGReachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PHGReachability.m; sourceTree = ""; }; - EAA5427F1EB4382100945DA7 /* StoreKitCapturerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StoreKitCapturerTests.swift; path = PostHogTests/StoreKitCapturerTests.swift; sourceTree = SOURCE_ROOT; }; - EADEB85B1DECD080005322DA /* PostHog.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PostHog.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - EADEB85E1DECD080005322DA /* PostHog.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PostHog.h; sourceTree = ""; }; - EADEB85F1DECD080005322DA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - EADEB86A1DECD0EF005322DA /* PostHogTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PostHogTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - EADEB8A91DECD12B005322DA /* PHGPostHog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PHGPostHog.h; sourceTree = ""; }; - EADEB8AA1DECD12B005322DA /* PHGPostHog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PHGPostHog.m; sourceTree = ""; }; - EADEB8AB1DECD12B005322DA /* PHGPostHogConfiguration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PHGPostHogConfiguration.h; sourceTree = ""; }; - EADEB8AC1DECD12B005322DA /* PHGPostHogConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PHGPostHogConfiguration.m; sourceTree = ""; }; - EADEB8E01DECD335005322DA /* HTTPClientTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPClientTest.swift; sourceTree = ""; }; - EADEB8E21DECD335005322DA /* NSData+PHGGUNZIPP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+PHGGUNZIPP.h"; sourceTree = ""; }; - EADEB8E31DECD335005322DA /* NSData+PHGGUNZIPP.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+PHGGUNZIPP.m"; sourceTree = ""; }; - EADEB8E41DECD335005322DA /* TestUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestUtils.swift; sourceTree = ""; }; - EADEB8E51DECD335005322DA /* FileStorageTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileStorageTest.swift; sourceTree = ""; }; - EADEB8E61DECD335005322DA /* UserDefaultsStorageTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDefaultsStorageTest.swift; sourceTree = ""; }; - EADEB8E71DECD335005322DA /* PostHogTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostHogTests.swift; sourceTree = ""; }; - EADEB8E81DECD335005322DA /* ContextTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextTest.swift; sourceTree = ""; }; - EADEB8E91DECD335005322DA /* CryptoTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryptoTest.swift; sourceTree = ""; }; - EADEB8EA1DECD335005322DA /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - EADEB8EC1DECD335005322DA /* PostHogTests-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "PostHogTests-Bridging-Header.h"; sourceTree = ""; }; - EADEB8FB1DECD521005322DA /* LSMatcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = LSMatcher.h; path = Pods/Nocilla/Nocilla/Matchers/LSMatcher.h; sourceTree = SOURCE_ROOT; }; - EADEB8FF1DED3711005322DA /* circle.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = circle.yml; sourceTree = ""; }; - EADEB9001DED3711005322DA /* PostHog.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = PostHog.podspec; sourceTree = ""; }; - EADEB9011DED3711005322DA /* Podfile.lock */ = {isa = PBXFileReference; lastKnownFileType = text; path = Podfile.lock; sourceTree = ""; }; - ECEED67EB700C2EFC866D7E0 /* Pods-PostHog.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PostHog.release.xcconfig"; path = "Target Support Files/Pods-PostHog/Pods-PostHog.release.xcconfig"; sourceTree = ""; }; - F2A54FAF28772F97006C53FF /* FeatureFlagTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlagTests.swift; sourceTree = ""; }; - F2A54FB22877582E006C53FF /* PHGGroupPayload.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PHGGroupPayload.m; sourceTree = ""; }; - F2A54FB42877583C006C53FF /* PHGGroupPayload.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PHGGroupPayload.h; sourceTree = ""; }; - F61EB4AE1F996DEF0038C8C0 /* PostHogUtilTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostHogUtilTests.swift; sourceTree = ""; }; + 3A0F108229C47940002C0084 /* UIViewExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewExample.swift; sourceTree = ""; }; + 3A0F108429C9ABB6002C0084 /* ReadWriteLock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadWriteLock.swift; sourceTree = ""; }; + 3A0F108829C9BD76002C0084 /* Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; + 3A580B4229E489D000C5C6F3 /* URLSession+body.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSession+body.swift"; sourceTree = ""; }; + 3A62646929C9E385007E8C07 /* MockPostHogServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPostHogServer.swift; sourceTree = ""; }; + 3A62647029CAF67B007E8C07 /* PostHogSessionManagerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogSessionManagerTest.swift; sourceTree = ""; }; + 3A62647429CB0168007E8C07 /* TestPostHog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestPostHog.swift; sourceTree = ""; }; + 3AA34CF7296D951A003398F4 /* PostHogExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PostHogExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 3AA34CF9296D951A003398F4 /* PostHogExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogExampleApp.swift; sourceTree = ""; }; + 3AA34CFB296D951A003398F4 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 3AA34CFD296D951B003398F4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 3AA34D00296D951B003398F4 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 3AA34D16296D9993003398F4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 3AAFB13129C0C699004F485B /* Package.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; + 3AC745B5296D6FE60025C109 /* PostHog.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PostHog.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3AC745B8296D6FE60025C109 /* PostHog.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PostHog.h; sourceTree = ""; }; + 3AC745BF296D6FE60025C109 /* PostHogTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PostHogTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3AE3FB2B2991320300AFFC18 /* Api.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Api.swift; sourceTree = ""; }; + 3AE3FB322991388500AFFC18 /* PostHogQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogQueue.swift; sourceTree = ""; }; + 3AE3FB36299162EA00AFFC18 /* PostHogApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogApi.swift; sourceTree = ""; }; + 3AE3FB3C29924E8200AFFC18 /* PostHogSDK.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogSDK.swift; sourceTree = ""; }; + 3AE3FB3E29924F4F00AFFC18 /* PostHogConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogConfig.swift; sourceTree = ""; }; + 3AE3FB422992985A00AFFC18 /* Reachability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reachability.swift; sourceTree = ""; }; + 3AE3FB462992AB0000AFFC18 /* Hedgelog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Hedgelog.swift; sourceTree = ""; }; + 3AE3FB48299391DF00AFFC18 /* PostHogStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogStorage.swift; sourceTree = ""; }; + 3AE3FB4A2993A68500AFFC18 /* PostHogStorageTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogStorageTest.swift; sourceTree = ""; }; + 3AE3FB4D2993D1D600AFFC18 /* PostHogSessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogSessionManager.swift; sourceTree = ""; }; + 690FF02F2AE7C5BA00A0B06B /* PostHogExampleWithPods.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PostHogExampleWithPods.xcodeproj; path = PostHogExampleWithPods/PostHogExampleWithPods.xcodeproj; sourceTree = ""; }; + 690FF0532AE7DB3700A0B06B /* PostHogExampleWithSPM.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PostHogExampleWithSPM.xcodeproj; path = PostHogExampleWithSPM/PostHogExampleWithSPM.xcodeproj; sourceTree = ""; }; + 690FF05E2AE7E2D400A0B06B /* Data+Gzip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Gzip.swift"; sourceTree = ""; }; + 690FF0AE2AEB9C1400A0B06B /* DateUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateUtils.swift; sourceTree = ""; }; + 690FF0B42AEBBD3C00A0B06B /* DictUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DictUtils.swift; sourceTree = ""; }; + 690FF0BA2AEF8B8200A0B06B /* PostHogContextTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogContextTest.swift; sourceTree = ""; }; + 690FF0BC2AEF93F400A0B06B /* PostHogFeatureFlagsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogFeatureFlagsTest.swift; sourceTree = ""; }; + 690FF0BE2AEFA97F00A0B06B /* FileUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = ""; }; + 690FF0C42AEFAE8200A0B06B /* PostHogLegacyQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogLegacyQueue.swift; sourceTree = ""; }; + 690FF0DE2AEFBC5700A0B06B /* PostHogLegacyQueueTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogLegacyQueueTest.swift; sourceTree = ""; }; + 690FF0E02AEFC59100A0B06B /* PostHogFileBackedQueueTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogFileBackedQueueTest.swift; sourceTree = ""; }; + 690FF0E22AEFD12900A0B06B /* PostHogConfigTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogConfigTest.swift; sourceTree = ""; }; + 690FF0E82AEFD3BD00A0B06B /* PostHogQueueTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogQueueTest.swift; sourceTree = ""; }; + 690FF0F42AF0F06100A0B06B /* PostHogSDKTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogSDKTest.swift; sourceTree = ""; }; + 690FF1732AF3CE8A00A0B06B /* PostHogExampleWatchOS.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = PostHogExampleWatchOS.xcodeproj; path = PostHogExampleWatchOS/PostHogExampleWatchOS.xcodeproj; sourceTree = ""; }; + 69261D122AD5685B00232EC7 /* PostHogFeatureFlags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogFeatureFlags.swift; sourceTree = ""; }; + 69261D182AD9673500232EC7 /* PostHogBatchUploadInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogBatchUploadInfo.swift; sourceTree = ""; }; + 69261D1A2AD9678C00232EC7 /* PostHogEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogEvent.swift; sourceTree = ""; }; + 69261D1C2AD967CD00232EC7 /* PostHogFileBackedQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogFileBackedQueue.swift; sourceTree = ""; }; + 69261D1E2AD9681300232EC7 /* PostHogConsumerPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogConsumerPayload.swift; sourceTree = ""; }; + 69261D222AD9784200232EC7 /* PostHogVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogVersion.swift; sourceTree = ""; }; + 69261D242AD9787A00232EC7 /* PostHogExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogExtensions.swift; sourceTree = ""; }; + 6926DA8D2ADD2876005760D2 /* PostHogContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogContext.swift; sourceTree = ""; }; + 69278D322AE6BC7100BB541A /* PostHogObjCExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PostHogObjCExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 69278D342AE6BC7100BB541A /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 69278D352AE6BC7100BB541A /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 69278D372AE6BC7100BB541A /* SceneDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SceneDelegate.h; sourceTree = ""; }; + 69278D382AE6BC7100BB541A /* SceneDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SceneDelegate.m; sourceTree = ""; }; + 69278D3A2AE6BC7100BB541A /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 69278D3B2AE6BC7100BB541A /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 69278D3E2AE6BC7100BB541A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 69278D402AE6BC7200BB541A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 69278D432AE6BC7200BB541A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 69278D452AE6BC7200BB541A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 69278D462AE6BC7200BB541A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 69779BEB2AE68E6900D7A48E /* UIViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 9D8CE59F23EE014E00197D0C /* Frameworks */ = { + 3AA34CF4296D951A003398F4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 3A81BEAE297704F400A6908D /* PostHog.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3AC745B2296D6FE60025C109 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9D8CE5A023EE014E00197D0C /* PostHog.framework in Frameworks */, - DF146944722A2847072DB568 /* Pods_PostHog_PostHogTestsTVOS.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - EADEB8571DECD080005322DA /* Frameworks */ = { + 3AC745BC296D6FE60025C109 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 2909CFFA247568CF00977728 /* CoreTelephony.framework in Frameworks */, - E1D8958848D3141E00E771F3 /* Pods_PostHog.framework in Frameworks */, + 690FF0F12AEFF24200A0B06B /* OHHTTPStubs in Frameworks */, + 690FF0ED2AEFF23300A0B06B /* Nimble in Frameworks */, + 690FF0EF2AEFF23D00A0B06B /* OHHTTPStubsSwift in Frameworks */, + 690FF0EB2AEFF22F00A0B06B /* Quick in Frameworks */, + 3AC745C0296D6FE60025C109 /* PostHog.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - EADEB8671DECD0EF005322DA /* Frameworks */ = { + 69278D2F2AE6BC7100BB541A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - EADEB86F1DECD0EF005322DA /* PostHog.framework in Frameworks */, - 4CD0E7D07BBBB59FF4827356 /* Pods_PostHog_PostHogTests.framework in Frameworks */, + 69278D4B2AE6BC9000BB541A /* PostHog.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 14BF89C893339D4171174443 /* Pods */ = { + 3A0F108129C4792E002C0084 /* Views */ = { isa = PBXGroup; children = ( - 12D0B9751E2B4493AC98D46C /* Pods-PostHog.debug.xcconfig */, - ECEED67EB700C2EFC866D7E0 /* Pods-PostHog.release.xcconfig */, - D5D6B45A7B89238D0787DFF6 /* Pods-PostHog-PostHogTests.debug.xcconfig */, - 27F9199C916C04653B965505 /* Pods-PostHog-PostHogTests.release.xcconfig */, - CCEFC40BEE77A69F2C598C9D /* Pods-PostHog-PostHogTestsTVOS.debug.xcconfig */, - 13F1CD8F13E34252559F5A83 /* Pods-PostHog-PostHogTestsTVOS.release.xcconfig */, + 3A0F108229C47940002C0084 /* UIViewExample.swift */, ); - path = Pods; + path = Views; sourceTree = ""; }; - E7F5B7E85686966D7A4D0284 /* Frameworks */ = { + 3A62646829C9E37A007E8C07 /* TestUtils */ = { isa = PBXGroup; children = ( - 29BC6B32244DA78E00D7C963 /* CoreTelephony.framework */, - 5C006A95003B78D95FBA5AB2 /* Pods_PostHog.framework */, - 3B1EB21C600E7907C3479131 /* Pods_PostHog_PostHogTests.framework */, - 73B8456977204A7F09DC6374 /* Pods_PostHog_PostHogTestsTVOS.framework */, + 3A62646929C9E385007E8C07 /* MockPostHogServer.swift */, + 3A62647429CB0168007E8C07 /* TestPostHog.swift */, + 3A580B4229E489D000C5C6F3 /* URLSession+body.swift */, ); - name = Frameworks; + path = TestUtils; sourceTree = ""; }; - EAA5427A1EB42B8C00945DA7 /* Vendor */ = { + 3AA34CF8296D951A003398F4 /* PostHogExample */ = { isa = PBXGroup; children = ( - EAA5427B1EB42B8C00945DA7 /* PHGReachability.h */, - EAA5427C1EB42B8C00945DA7 /* PHGReachability.m */, - BF8C8388F05135177F79C286 /* ObjC.m */, - BF8C88E533159E768C65C355 /* ObjC.h */, + 3A0F108129C4792E002C0084 /* Views */, + 3AA34CF9296D951A003398F4 /* PostHogExampleApp.swift */, + 3AE3FB2B2991320300AFFC18 /* Api.swift */, + 3AA34D16296D9993003398F4 /* AppDelegate.swift */, + 3AA34CFB296D951A003398F4 /* ContentView.swift */, + 3AA34CFD296D951B003398F4 /* Assets.xcassets */, + 3AA34CFF296D951B003398F4 /* Preview Content */, ); - name = Vendor; - path = PostHog/Vendor; - sourceTree = SOURCE_ROOT; + path = PostHogExample; + sourceTree = ""; }; - EADEB8511DECD080005322DA = { + 3AA34CFF296D951B003398F4 /* Preview Content */ = { isa = PBXGroup; children = ( - EADEB85D1DECD080005322DA /* PostHog */, - EADEB86B1DECD0EF005322DA /* PostHogTests */, - EADEB85C1DECD080005322DA /* Products */, - EADEB8FE1DED36ED005322DA /* Supporting Files */, - 14BF89C893339D4171174443 /* Pods */, - E7F5B7E85686966D7A4D0284 /* Frameworks */, - BF8C89860ED4391E417B39E9 /* CHANGELOG.md */, + 3AA34D00296D951B003398F4 /* Preview Assets.xcassets */, ); + path = "Preview Content"; sourceTree = ""; }; - EADEB85C1DECD080005322DA /* Products */ = { + 3AA4C09B2988315D006C4731 /* Utils */ = { isa = PBXGroup; children = ( - EADEB85B1DECD080005322DA /* PostHog.framework */, - EADEB86A1DECD0EF005322DA /* PostHogTests.xctest */, - 9D8CE5A723EE014E00197D0C /* PostHogTestsTVOS.xctest */, + 3AE3FB422992985A00AFFC18 /* Reachability.swift */, + 3AE3FB462992AB0000AFFC18 /* Hedgelog.swift */, + 3A0F108429C9ABB6002C0084 /* ReadWriteLock.swift */, + 3A0F108829C9BD76002C0084 /* Errors.swift */, + 690FF05E2AE7E2D400A0B06B /* Data+Gzip.swift */, + 690FF0BE2AEFA97F00A0B06B /* FileUtils.swift */, + 690FF0B42AEBBD3C00A0B06B /* DictUtils.swift */, + 690FF0AE2AEB9C1400A0B06B /* DateUtils.swift */, + ); + path = Utils; + sourceTree = ""; + }; + 3AC745AB296D6FE60025C109 = { + isa = PBXGroup; + children = ( + 690FF1732AF3CE8A00A0B06B /* PostHogExampleWatchOS.xcodeproj */, + 690FF0532AE7DB3700A0B06B /* PostHogExampleWithSPM.xcodeproj */, + 690FF02F2AE7C5BA00A0B06B /* PostHogExampleWithPods.xcodeproj */, + 3AAFB13129C0C699004F485B /* Package.swift */, + 3AC745B7296D6FE60025C109 /* PostHog */, + 3AC745C3296D6FE60025C109 /* PostHogTests */, + 3AA34CF8296D951A003398F4 /* PostHogExample */, + 69278D332AE6BC7100BB541A /* PostHogObjCExample */, + 3AC745B6296D6FE60025C109 /* Products */, + 69261D152AD92D6C00232EC7 /* Frameworks */, + ); + sourceTree = ""; + }; + 3AC745B6296D6FE60025C109 /* Products */ = { + isa = PBXGroup; + children = ( + 3AC745B5296D6FE60025C109 /* PostHog.framework */, + 3AC745BF296D6FE60025C109 /* PostHogTests.xctest */, + 3AA34CF7296D951A003398F4 /* PostHogExample.app */, + 69278D322AE6BC7100BB541A /* PostHogObjCExample.app */, ); name = Products; sourceTree = ""; }; - EADEB85D1DECD080005322DA /* PostHog */ = { + 3AC745B7296D6FE60025C109 /* PostHog */ = { isa = PBXGroup; children = ( - 3A3AE0B22A29C63800556065 /* PHGApplicationUtils.h */, - 3A3AE0B32A29C63800556065 /* PHGApplicationUtils.m */, - EADEB8751DECD12B005322DA /* Classes */, - EADEB85E1DECD080005322DA /* PostHog.h */, - EADEB85F1DECD080005322DA /* Info.plist */, - BF8C81224E7FCD6D6BDF4614 /* PHGAES256Crypto.h */, - BF8C861A6C2B0C33AC1E7D34 /* PHGAES256Crypto.m */, - BF8C8EB90563BC3956BA25E4 /* PHGFileStorage.m */, - BF8C8D864F905E2A4AF297E8 /* UIViewController+PHGScreen.m */, - BF8C811FC65903D9C4661465 /* PHGPostHogIntegration.h */, - BF8C81ED70A463EA7799B9B6 /* PHGUtils.h */, - BF8C8EF9E0ABC31A6E3C0868 /* PHGUtils.m */, - BF8C8EC5CFC52D2FDC180500 /* PHGMacros.h */, - BF8C8A9AD350CD86D85DF182 /* PHGHTTPClient.h */, - BF8C8AC249BECDAF13435FD2 /* UIViewController+PHGScreen.h */, - BF8C86671862A5FFA20C721C /* PHGFileStorage.h */, - BF8C8CD3AE863FA75ADF0189 /* PHGPostHogUtils.m */, - BF8C83BC7E9D748CA963E2C1 /* PHGStorage.h */, - BF8C8F47D9775221FE48AA4D /* NSData+PHGGZIP.h */, - BF8C8C0E43DD950DB9CA7725 /* PHGPostHogUtils.h */, - BF8C8CFB8B293A63E897C5A8 /* PHGUserDefaultsStorage.m */, - BF8C8233411FABE41CC0964B /* PHGStoreKitCapturer.m */, - BF8C8CBED16F21362625BE46 /* PHGStoreKitCapturer.h */, - BF8C802EB41E4E93050D28BA /* NSData+PHGGZIP.m */, - BF8C8BFC78C6AE9EB9732E26 /* PHGHTTPClient.m */, - BF8C8AF3C1D249548CF69443 /* PHGPostHogIntegration.m */, - BF8C8E50F0C96453793BC955 /* PHGUserDefaultsStorage.h */, + 69779BED2AE6B29E00D7A48E /* Models */, + 3AC745B8296D6FE60025C109 /* PostHog.h */, + 3AA4C09B2988315D006C4731 /* Utils */, + 3AE3FB36299162EA00AFFC18 /* PostHogApi.swift */, + 3AE3FB322991388500AFFC18 /* PostHogQueue.swift */, + 3AE3FB3C29924E8200AFFC18 /* PostHogSDK.swift */, + 3AE3FB3E29924F4F00AFFC18 /* PostHogConfig.swift */, + 3AE3FB4D2993D1D600AFFC18 /* PostHogSessionManager.swift */, + 3AE3FB48299391DF00AFFC18 /* PostHogStorage.swift */, + 69261D122AD5685B00232EC7 /* PostHogFeatureFlags.swift */, + 69261D182AD9673500232EC7 /* PostHogBatchUploadInfo.swift */, + 69261D1C2AD967CD00232EC7 /* PostHogFileBackedQueue.swift */, + 69261D1E2AD9681300232EC7 /* PostHogConsumerPayload.swift */, + 69261D222AD9784200232EC7 /* PostHogVersion.swift */, + 69261D242AD9787A00232EC7 /* PostHogExtensions.swift */, + 6926DA8D2ADD2876005760D2 /* PostHogContext.swift */, + 69779BEB2AE68E6900D7A48E /* UIViewController.swift */, + 690FF0C42AEFAE8200A0B06B /* PostHogLegacyQueue.swift */, ); path = PostHog; sourceTree = ""; }; - EADEB86B1DECD0EF005322DA /* PostHogTests */ = { + 3AC745C3296D6FE60025C109 /* PostHogTests */ = { isa = PBXGroup; children = ( - EADEB8E11DECD335005322DA /* Utils */, - EADEB8FB1DECD521005322DA /* LSMatcher.h */, - EADEB8E01DECD335005322DA /* HTTPClientTest.swift */, - EAA5427F1EB4382100945DA7 /* StoreKitCapturerTests.swift */, - EADEB8E51DECD335005322DA /* FileStorageTest.swift */, - EADEB8E61DECD335005322DA /* UserDefaultsStorageTest.swift */, - EA8F09731E24C5C600B8B93F /* MiddlewareTests.swift */, - EADEB8E71DECD335005322DA /* PostHogTests.swift */, - F61EB4AE1F996DEF0038C8C0 /* PostHogUtilTests.swift */, - EAA542761EB4035400945DA7 /* CapturingTests.swift */, - EADEB8E81DECD335005322DA /* ContextTest.swift */, - EADEB8E91DECD335005322DA /* CryptoTest.swift */, - EADEB8EA1DECD335005322DA /* Info.plist */, - EADEB8EC1DECD335005322DA /* PostHogTests-Bridging-Header.h */, - 6EEC1C702017EA370089C478 /* EndToEndTests.swift */, - A31958EE2385AC3A00A47EFA /* SerializationTests.m */, - F2A54FAF28772F97006C53FF /* FeatureFlagTests.swift */, + 3A62646829C9E37A007E8C07 /* TestUtils */, + 3AE3FB4A2993A68500AFFC18 /* PostHogStorageTest.swift */, + 3A62647029CAF67B007E8C07 /* PostHogSessionManagerTest.swift */, + 690FF0BA2AEF8B8200A0B06B /* PostHogContextTest.swift */, + 690FF0BC2AEF93F400A0B06B /* PostHogFeatureFlagsTest.swift */, + 690FF0DE2AEFBC5700A0B06B /* PostHogLegacyQueueTest.swift */, + 690FF0E02AEFC59100A0B06B /* PostHogFileBackedQueueTest.swift */, + 690FF0E22AEFD12900A0B06B /* PostHogConfigTest.swift */, + 690FF0E82AEFD3BD00A0B06B /* PostHogQueueTest.swift */, + 690FF0F42AF0F06100A0B06B /* PostHogSDKTest.swift */, ); - indentWidth = 2; path = PostHogTests; sourceTree = ""; - tabWidth = 2; }; - EADEB8751DECD12B005322DA /* Classes */ = { + 690FF0302AE7C5BA00A0B06B /* Products */ = { isa = PBXGroup; children = ( - EAA5427A1EB42B8C00945DA7 /* Vendor */, - EA88A5971DED7608009FB66A /* PHGSerializableValue.h */, - EADEB8A91DECD12B005322DA /* PHGPostHog.h */, - EADEB8AA1DECD12B005322DA /* PHGPostHog.m */, - EADEB8AB1DECD12B005322DA /* PHGPostHogConfiguration.h */, - EADEB8AC1DECD12B005322DA /* PHGPostHogConfiguration.m */, - BF8C8DC90BD0C5D0AF19498F /* PHGIntegration.h */, - BF8C8BC32FD2DF9975AB3A35 /* PHGAliasPayload.m */, - BF8C8E6F09398C169F35E675 /* PHGPayloadManager.h */, - BF8C8FC3DCC9BEBB4474F373 /* PHGIdentifyPayload.h */, - BF8C80633A9C834A27298FDE /* PHGPayload.m */, - BF8C83FDEE5C86782CB11D8B /* PHGScreenPayload.m */, - BF8C8E2E6C04BE91346F441D /* PHGPayload.h */, - BF8C8CD474E1A05E0D0F8C88 /* PHGPayloadManager.m */, - BF8C8D451AA492DA225B7DC7 /* PHGCapturePayload.m */, - BF8C82BFA5872F7E8CBFA682 /* PHGScreenPayload.h */, - BF8C8558DAB439462566DA94 /* PHGAliasPayload.h */, - BF8C811B41CBE38598366297 /* PHGIdentifyPayload.m */, - BF8C8EE82249B851E06328EB /* PHGCapturePayload.h */, - BF8C8F6DD6E7763114B6FCD1 /* PHGContext.m */, - BF8C8AAA842A95EC8B4ED380 /* PHGMiddleware.m */, - BF8C8BD43F67239FA1902206 /* PHGContext.h */, - BF8C81A4093856C696EF943D /* PHGMiddleware.h */, - BF8C89BCDF12D1B64E9475F1 /* PHGCrypto.h */, - F2A54FB22877582E006C53FF /* PHGGroupPayload.m */, - F2A54FB42877583C006C53FF /* PHGGroupPayload.h */, + 690FF0342AE7C5BB00A0B06B /* PostHogExampleWithPods.app */, ); - path = Classes; + name = Products; sourceTree = ""; }; - EADEB8E11DECD335005322DA /* Utils */ = { + 690FF0542AE7DB3700A0B06B /* Products */ = { isa = PBXGroup; children = ( - EADEB8E21DECD335005322DA /* NSData+PHGGUNZIPP.h */, - EADEB8E31DECD335005322DA /* NSData+PHGGUNZIPP.m */, - EADEB8E41DECD335005322DA /* TestUtils.swift */, + 690FF0582AE7DB3700A0B06B /* PostHogExampleWithSPM.app */, ); - path = Utils; + name = Products; + sourceTree = ""; + }; + 690FF1742AF3CE8A00A0B06B /* Products */ = { + isa = PBXGroup; + children = ( + 690FF1792AF3CE8B00A0B06B /* PostHogExampleWatchOS.app */, + 690FF17B2AF3CE8B00A0B06B /* PostHogExampleWatchOS Watch App.app */, + ); + name = Products; + sourceTree = ""; + }; + 69261D152AD92D6C00232EC7 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; + 69278D332AE6BC7100BB541A /* PostHogObjCExample */ = { + isa = PBXGroup; + children = ( + 69278D342AE6BC7100BB541A /* AppDelegate.h */, + 69278D352AE6BC7100BB541A /* AppDelegate.m */, + 69278D372AE6BC7100BB541A /* SceneDelegate.h */, + 69278D382AE6BC7100BB541A /* SceneDelegate.m */, + 69278D3A2AE6BC7100BB541A /* ViewController.h */, + 69278D3B2AE6BC7100BB541A /* ViewController.m */, + 69278D3D2AE6BC7100BB541A /* Main.storyboard */, + 69278D402AE6BC7200BB541A /* Assets.xcassets */, + 69278D422AE6BC7200BB541A /* LaunchScreen.storyboard */, + 69278D452AE6BC7200BB541A /* Info.plist */, + 69278D462AE6BC7200BB541A /* main.m */, + ); + path = PostHogObjCExample; sourceTree = ""; }; - EADEB8FE1DED36ED005322DA /* Supporting Files */ = { + 69779BED2AE6B29E00D7A48E /* Models */ = { isa = PBXGroup; children = ( - EADEB8FF1DED3711005322DA /* circle.yml */, - EADEB9001DED3711005322DA /* PostHog.podspec */, - EADEB9011DED3711005322DA /* Podfile.lock */, + 69261D1A2AD9678C00232EC7 /* PostHogEvent.swift */, ); - name = "Supporting Files"; + path = Models; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ - EADEB8581DECD080005322DA /* Headers */ = { + 3AC745B0296D6FE60025C109 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - EADEB8DC1DECD12B005322DA /* PHGPostHog.h in Headers */, - EA88A5981DED7608009FB66A /* PHGSerializableValue.h in Headers */, - EADEB8DE1DECD12B005322DA /* PHGPostHogConfiguration.h in Headers */, - BF8C8F2782C85B046B01C044 /* PHGIntegration.h in Headers */, - EADEB8601DECD080005322DA /* PostHog.h in Headers */, - EAA5427D1EB42B8C00945DA7 /* PHGReachability.h in Headers */, - BF8C826724E6F30D02E53EA6 /* ObjC.h in Headers */, - BF8C87E7C76982625BA92F48 /* PHGAES256Crypto.h in Headers */, - BF8C850E6C20F8E009469D1E /* PHGPostHogIntegration.h in Headers */, - BF8C8DC55C0552CCAFE7BEBF /* PHGMacros.h in Headers */, - BF8C84098D3C47C6F7B7FF9A /* PHGHTTPClient.h in Headers */, - BF8C817D68B4E9B441E21D64 /* UIViewController+PHGScreen.h in Headers */, - F2A54FB52877583C006C53FF /* PHGGroupPayload.h in Headers */, - BF8C8AD1FBFBB6941DE29D3B /* PHGFileStorage.h in Headers */, - BF8C845A65AB5AD06E1EE26B /* PHGStorage.h in Headers */, - BF8C890FADEC88022E7A3588 /* NSData+PHGGZIP.h in Headers */, - BF8C8181F96E1C8CB86D783D /* PHGPostHogUtils.h in Headers */, - BF8C8FCEC80B47BF2236236E /* PHGUtils.h in Headers */, - BF8C8DCC18E3AA39CCB330BC /* PHGStoreKitCapturer.h in Headers */, - BF8C84DEF5634524F1F9110A /* PHGUserDefaultsStorage.h in Headers */, - BF8C806E9390DA2FC00D1812 /* PHGPayloadManager.h in Headers */, - BF8C86649922EFA5F66C5BE7 /* PHGIdentifyPayload.h in Headers */, - BF8C8CAB52120583B9BB4934 /* PHGPayload.h in Headers */, - BF8C87A812BB60E58DB7B1E4 /* PHGScreenPayload.h in Headers */, - BF8C838C46F024993F46C108 /* PHGAliasPayload.h in Headers */, - BF8C8791A47E4BF8586F0B43 /* PHGCapturePayload.h in Headers */, - BF8C814E5A3BA7A84540D733 /* PHGContext.h in Headers */, - BF8C8AEC72DA925C8B65B1A8 /* PHGMiddleware.h in Headers */, - BF8C8D8EE891F5599F3D78D7 /* PHGCrypto.h in Headers */, - 3A3AE0B42A29C63800556065 /* PHGApplicationUtils.h in Headers */, + 3AC745C6296D6FE60025C109 /* PostHog.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ - 9D8CE58B23EE014E00197D0C /* PostHogTestsTVOS */ = { + 3AA34CF6296D951A003398F4 /* PostHogExample */ = { isa = PBXNativeTarget; - buildConfigurationList = 9D8CE5A423EE014E00197D0C /* Build configuration list for PBXNativeTarget "PostHogTestsTVOS" */; + buildConfigurationList = 3AA34D02296D951B003398F4 /* Build configuration list for PBXNativeTarget "PostHogExample" */; buildPhases = ( - A52C2A47C02C37ECEE7A9231 /* [CP] Check Pods Manifest.lock */, - 9D8CE58F23EE014E00197D0C /* Sources */, - 9D8CE59F23EE014E00197D0C /* Frameworks */, - 9D8CE5A223EE014E00197D0C /* Resources */, - 071D38B62353AEF7B53D282D /* [CP] Embed Pods Frameworks */, + 3AA34CF3296D951A003398F4 /* Sources */, + 3AA34CF4296D951A003398F4 /* Frameworks */, + 3AA34CF5296D951A003398F4 /* Resources */, + 3A81BEB2297704F400A6908D /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( - 9D8CE58C23EE014E00197D0C /* PBXTargetDependency */, + 3A81BEB1297704F400A6908D /* PBXTargetDependency */, ); - name = PostHogTestsTVOS; - productName = PostHogTests; - productReference = 9D8CE5A723EE014E00197D0C /* PostHogTestsTVOS.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; + name = PostHogExample; + productName = PostHogExample; + productReference = 3AA34CF7296D951A003398F4 /* PostHogExample.app */; + productType = "com.apple.product-type.application"; }; - EADEB85A1DECD080005322DA /* PostHog */ = { + 3AC745B4296D6FE60025C109 /* PostHog */ = { isa = PBXNativeTarget; - buildConfigurationList = EADEB8631DECD080005322DA /* Build configuration list for PBXNativeTarget "PostHog" */; + buildConfigurationList = 3AC745C9296D6FE60025C109 /* Build configuration list for PBXNativeTarget "PostHog" */; buildPhases = ( - 56D7CAAD4B7DFBDE2F4E900E /* [CP] Check Pods Manifest.lock */, - EADEB8561DECD080005322DA /* Sources */, - EADEB8571DECD080005322DA /* Frameworks */, - EADEB8581DECD080005322DA /* Headers */, - EADEB8591DECD080005322DA /* Resources */, + 3AC745B0296D6FE60025C109 /* Headers */, + 3AC745B1296D6FE60025C109 /* Sources */, + 3AC745B2296D6FE60025C109 /* Frameworks */, + 3AC745B3296D6FE60025C109 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = PostHog; + packageProductDependencies = ( + ); productName = PostHog; - productReference = EADEB85B1DECD080005322DA /* PostHog.framework */; + productReference = 3AC745B5296D6FE60025C109 /* PostHog.framework */; productType = "com.apple.product-type.framework"; }; - EADEB8691DECD0EF005322DA /* PostHogTests */ = { + 3AC745BE296D6FE60025C109 /* PostHogTests */ = { isa = PBXNativeTarget; - buildConfigurationList = EADEB8741DECD0EF005322DA /* Build configuration list for PBXNativeTarget "PostHogTests" */; + buildConfigurationList = 3AC745CC296D6FE60025C109 /* Build configuration list for PBXNativeTarget "PostHogTests" */; buildPhases = ( - 001D6D8F9080455DC948E397 /* [CP] Check Pods Manifest.lock */, - EADEB8661DECD0EF005322DA /* Sources */, - EADEB8671DECD0EF005322DA /* Frameworks */, - EADEB8681DECD0EF005322DA /* Resources */, - BEC2F2CF1B4160125DC0BCD8 /* [CP] Embed Pods Frameworks */, + 3AC745BB296D6FE60025C109 /* Sources */, + 3AC745BC296D6FE60025C109 /* Frameworks */, + 3AC745BD296D6FE60025C109 /* Resources */, ); buildRules = ( ); dependencies = ( - EADEB8711DECD0EF005322DA /* PBXTargetDependency */, + 3AC745C2296D6FE60025C109 /* PBXTargetDependency */, + 3AA34D06296D9523003398F4 /* PBXTargetDependency */, ); name = PostHogTests; + packageProductDependencies = ( + 690FF0EA2AEFF22F00A0B06B /* Quick */, + 690FF0EC2AEFF23300A0B06B /* Nimble */, + 690FF0EE2AEFF23D00A0B06B /* OHHTTPStubsSwift */, + 690FF0F02AEFF24200A0B06B /* OHHTTPStubs */, + ); productName = PostHogTests; - productReference = EADEB86A1DECD0EF005322DA /* PostHogTests.xctest */; + productReference = 3AC745BF296D6FE60025C109 /* PostHogTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + 69278D312AE6BC7100BB541A /* PostHogObjCExample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 69278D482AE6BC7200BB541A /* Build configuration list for PBXNativeTarget "PostHogObjCExample" */; + buildPhases = ( + 69278D2E2AE6BC7100BB541A /* Sources */, + 69278D2F2AE6BC7100BB541A /* Frameworks */, + 69278D302AE6BC7100BB541A /* Resources */, + 69278D4F2AE6BC9000BB541A /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 69278D4E2AE6BC9000BB541A /* PBXTargetDependency */, + ); + name = PostHogObjCExample; + productName = PostHogObjCExample; + productReference = 69278D322AE6BC7100BB541A /* PostHogObjCExample.app */; + productType = "com.apple.product-type.application"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ - EADEB8521DECD080005322DA /* Project object */ = { + 3AC745AC296D6FE60025C109 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0810; - LastUpgradeCheck = 1140; - ORGANIZATIONNAME = PostHog; + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1420; + LastUpgradeCheck = 1500; TargetAttributes = { - 9D8CE58B23EE014E00197D0C = { - DevelopmentTeam = UG8G36W2HB; + 3AA34CF6296D951A003398F4 = { + CreatedOnToolsVersion = 14.2; }; - EADEB85A1DECD080005322DA = { - CreatedOnToolsVersion = 8.1; - DevelopmentTeam = UG8G36W2HB; - ProvisioningStyle = Automatic; + 3AC745B4296D6FE60025C109 = { + CreatedOnToolsVersion = 14.2; + LastSwiftMigration = 1420; }; - EADEB8691DECD0EF005322DA = { - CreatedOnToolsVersion = 8.1; - DevelopmentTeam = UG8G36W2HB; - ProvisioningStyle = Automatic; + 3AC745BE296D6FE60025C109 = { + CreatedOnToolsVersion = 14.2; + LastSwiftMigration = 1420; + }; + 69278D312AE6BC7100BB541A = { + CreatedOnToolsVersion = 15.0.1; }; }; }; - buildConfigurationList = EADEB8551DECD080005322DA /* Build configuration list for PBXProject "PostHog" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + buildConfigurationList = 3AC745AF296D6FE60025C109 /* Build configuration list for PBXProject "PostHog" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( - English, en, + Base, + ); + mainGroup = 3AC745AB296D6FE60025C109; + packageReferences = ( + 3A867B6E29C1DF73009D0852 /* XCRemoteSwiftPackageReference "Quick" */, + 3A867B7129C1DFEF009D0852 /* XCRemoteSwiftPackageReference "Nimble" */, + 3A62646529C9E36B007E8C07 /* XCRemoteSwiftPackageReference "Shock" */, + 3A580B3D29E481F200C5C6F3 /* XCRemoteSwiftPackageReference "OHHTTPStubs" */, ); - mainGroup = EADEB8511DECD080005322DA; - productRefGroup = EADEB85C1DECD080005322DA /* Products */; + productRefGroup = 3AC745B6296D6FE60025C109 /* Products */; projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 690FF1742AF3CE8A00A0B06B /* Products */; + ProjectRef = 690FF1732AF3CE8A00A0B06B /* PostHogExampleWatchOS.xcodeproj */; + }, + { + ProductGroup = 690FF0302AE7C5BA00A0B06B /* Products */; + ProjectRef = 690FF02F2AE7C5BA00A0B06B /* PostHogExampleWithPods.xcodeproj */; + }, + { + ProductGroup = 690FF0542AE7DB3700A0B06B /* Products */; + ProjectRef = 690FF0532AE7DB3700A0B06B /* PostHogExampleWithSPM.xcodeproj */; + }, + ); projectRoot = ""; targets = ( - EADEB85A1DECD080005322DA /* PostHog */, - EADEB8691DECD0EF005322DA /* PostHogTests */, - 9D8CE58B23EE014E00197D0C /* PostHogTestsTVOS */, + 3AC745B4296D6FE60025C109 /* PostHog */, + 3AC745BE296D6FE60025C109 /* PostHogTests */, + 3AA34CF6296D951A003398F4 /* PostHogExample */, + 69278D312AE6BC7100BB541A /* PostHogObjCExample */, ); }; /* End PBXProject section */ -/* Begin PBXResourcesBuildPhase section */ - 9D8CE5A223EE014E00197D0C /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; +/* Begin PBXReferenceProxy section */ + 690FF0342AE7C5BB00A0B06B /* PostHogExampleWithPods.app */ = { + isa = PBXReferenceProxy; + fileType = wrapper.application; + path = PostHogExampleWithPods.app; + remoteRef = 690FF0332AE7C5BB00A0B06B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; }; - EADEB8591DECD080005322DA /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; + 690FF0582AE7DB3700A0B06B /* PostHogExampleWithSPM.app */ = { + isa = PBXReferenceProxy; + fileType = wrapper.application; + path = PostHogExampleWithSPM.app; + remoteRef = 690FF0572AE7DB3700A0B06B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; }; - EADEB8681DECD0EF005322DA /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; + 690FF1792AF3CE8B00A0B06B /* PostHogExampleWatchOS.app */ = { + isa = PBXReferenceProxy; + fileType = wrapper.application; + path = PostHogExampleWatchOS.app; + remoteRef = 690FF1782AF3CE8B00A0B06B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; }; -/* End PBXResourcesBuildPhase section */ + 690FF17B2AF3CE8B00A0B06B /* PostHogExampleWatchOS Watch App.app */ = { + isa = PBXReferenceProxy; + fileType = wrapper.application; + path = "PostHogExampleWatchOS Watch App.app"; + remoteRef = 690FF17A2AF3CE8B00A0B06B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ -/* Begin PBXShellScriptBuildPhase section */ - 001D6D8F9080455DC948E397 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; +/* Begin PBXResourcesBuildPhase section */ + 3AA34CF5296D951A003398F4 /* Resources */ = { + isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-PostHog-PostHogTests-checkManifestLockResult.txt", + 3AA34D01296D951B003398F4 /* Preview Assets.xcassets in Resources */, + 3AA34CFE296D951B003398F4 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; }; - 071D38B62353AEF7B53D282D /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; + 3AC745B3296D6FE60025C109 /* Resources */ = { + isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-PostHog-PostHogTestsTVOS/Pods-PostHog-PostHogTestsTVOS-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", - "${BUILT_PRODUCTS_DIR}/Alamofire-Synchronous/Alamofire_Synchronous.framework", - "${BUILT_PRODUCTS_DIR}/Nimble/Nimble.framework", - "${BUILT_PRODUCTS_DIR}/Nocilla/Nocilla.framework", - "${BUILT_PRODUCTS_DIR}/Quick/Quick.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire_Synchronous.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nimble.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nocilla.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Quick.framework", - ); runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PostHog-PostHogTestsTVOS/Pods-PostHog-PostHogTestsTVOS-frameworks.sh\"\n"; - showEnvVarsInLog = 0; }; - 56D7CAAD4B7DFBDE2F4E900E /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; + 3AC745BD296D6FE60025C109 /* Resources */ = { + isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-PostHog-checkManifestLockResult.txt", - ); runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; }; - A52C2A47C02C37ECEE7A9231 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; + 69278D302AE6BC7100BB541A /* Resources */ = { + isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-PostHog-PostHogTestsTVOS-checkManifestLockResult.txt", + 69278D442AE6BC7200BB541A /* LaunchScreen.storyboard in Resources */, + 69278D412AE6BC7200BB541A /* Assets.xcassets in Resources */, + 69278D3F2AE6BC7100BB541A /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; }; - BEC2F2CF1B4160125DC0BCD8 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 3AA34CF3296D951A003398F4 /* Sources */ = { + isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-PostHog-PostHogTests/Pods-PostHog-PostHogTests-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", - "${BUILT_PRODUCTS_DIR}/Alamofire-Synchronous/Alamofire_Synchronous.framework", - "${BUILT_PRODUCTS_DIR}/Nimble/Nimble.framework", - "${BUILT_PRODUCTS_DIR}/Nocilla/Nocilla.framework", - "${BUILT_PRODUCTS_DIR}/Quick/Quick.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire_Synchronous.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nimble.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nocilla.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Quick.framework", + 3AA34D17296D9993003398F4 /* AppDelegate.swift in Sources */, + 3AA34CFC296D951A003398F4 /* ContentView.swift in Sources */, + 3AE3FB2C2991320300AFFC18 /* Api.swift in Sources */, + 3A0F108329C47940002C0084 /* UIViewExample.swift in Sources */, + 3AA34CFA296D951A003398F4 /* PostHogExampleApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PostHog-PostHogTests/Pods-PostHog-PostHogTests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 9D8CE58F23EE014E00197D0C /* Sources */ = { + 3AC745B1296D6FE60025C109 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 9D8CE59023EE014E00197D0C /* CryptoTest.swift in Sources */, - 9D8CE59123EE014E00197D0C /* UserDefaultsStorageTest.swift in Sources */, - 9D8CE59223EE014E00197D0C /* CapturingTests.swift in Sources */, - 9D8CE59323EE014E00197D0C /* ContextTest.swift in Sources */, - 9D8CE59423EE014E00197D0C /* SerializationTests.m in Sources */, - F2A54FB128772F97006C53FF /* FeatureFlagTests.swift in Sources */, - 9D8CE59523EE014E00197D0C /* HTTPClientTest.swift in Sources */, - 9D8CE59623EE014E00197D0C /* PostHogTests.swift in Sources */, - 9D8CE59723EE014E00197D0C /* FileStorageTest.swift in Sources */, - 9D8CE59823EE014E00197D0C /* PostHogUtilTests.swift in Sources */, - 9D8CE59923EE014E00197D0C /* StoreKitCapturerTests.swift in Sources */, - 9D8CE59A23EE014E00197D0C /* MiddlewareTests.swift in Sources */, - 9D8CE59C23EE014E00197D0C /* NSData+PHGGUNZIPP.m in Sources */, - 9D8CE59D23EE014E00197D0C /* EndToEndTests.swift in Sources */, - 9D8CE59E23EE014E00197D0C /* TestUtils.swift in Sources */, + 690FF05F2AE7E2D400A0B06B /* Data+Gzip.swift in Sources */, + 69261D1F2AD9681300232EC7 /* PostHogConsumerPayload.swift in Sources */, + 690FF0BF2AEFA97F00A0B06B /* FileUtils.swift in Sources */, + 69261D252AD9787A00232EC7 /* PostHogExtensions.swift in Sources */, + 3AE3FB4E2993D1D600AFFC18 /* PostHogSessionManager.swift in Sources */, + 3AE3FB49299391DF00AFFC18 /* PostHogStorage.swift in Sources */, + 69261D232AD9784200232EC7 /* PostHogVersion.swift in Sources */, + 69779BEC2AE68E6900D7A48E /* UIViewController.swift in Sources */, + 3A0F108929C9BD76002C0084 /* Errors.swift in Sources */, + 3AE3FB37299162EA00AFFC18 /* PostHogApi.swift in Sources */, + 6926DA8E2ADD2876005760D2 /* PostHogContext.swift in Sources */, + 690FF0AF2AEB9C1400A0B06B /* DateUtils.swift in Sources */, + 69261D192AD9673500232EC7 /* PostHogBatchUploadInfo.swift in Sources */, + 3A0F108529C9ABB6002C0084 /* ReadWriteLock.swift in Sources */, + 3AE3FB3D29924E8200AFFC18 /* PostHogSDK.swift in Sources */, + 3AE3FB3F29924F4F00AFFC18 /* PostHogConfig.swift in Sources */, + 690FF0C52AEFAE8200A0B06B /* PostHogLegacyQueue.swift in Sources */, + 3AE3FB332991388500AFFC18 /* PostHogQueue.swift in Sources */, + 690FF0B52AEBBD3C00A0B06B /* DictUtils.swift in Sources */, + 69261D1B2AD9678C00232EC7 /* PostHogEvent.swift in Sources */, + 3AE3FB472992AB0000AFFC18 /* Hedgelog.swift in Sources */, + 69261D132AD5685B00232EC7 /* PostHogFeatureFlags.swift in Sources */, + 69261D1D2AD967CD00232EC7 /* PostHogFileBackedQueue.swift in Sources */, + 3AE3FB432992985A00AFFC18 /* Reachability.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - EADEB8561DECD080005322DA /* Sources */ = { + 3AC745BB296D6FE60025C109 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - EADEB8DD1DECD12B005322DA /* PHGPostHog.m in Sources */, - EADEB8DF1DECD12B005322DA /* PHGPostHogConfiguration.m in Sources */, - EAA5427E1EB42B8C00945DA7 /* PHGReachability.m in Sources */, - BF8C8C9C0FD46725EA63890A /* ObjC.m in Sources */, - 3A3AE0B52A29C63800556065 /* PHGApplicationUtils.m in Sources */, - BF8C8AF845410CCF3744EEA1 /* PHGAES256Crypto.m in Sources */, - BF8C8E57BD722DB6AEFF6835 /* PHGFileStorage.m in Sources */, - BF8C856B25377945554598DF /* UIViewController+PHGScreen.m in Sources */, - BF8C8EF6349359A33E8C6DFD /* PHGUtils.m in Sources */, - BF8C84CDF20A0959FC24E076 /* PHGPostHogUtils.m in Sources */, - F2A54FB32877582E006C53FF /* PHGGroupPayload.m in Sources */, - BF8C8E28834B124F2E9835A5 /* PHGUserDefaultsStorage.m in Sources */, - BF8C8FC2CB4FAE70F5879308 /* PHGStoreKitCapturer.m in Sources */, - BF8C8D75F1E684EA93578DF5 /* NSData+PHGGZIP.m in Sources */, - BF8C8D9B4A6389088569438F /* PHGHTTPClient.m in Sources */, - BF8C8A634BCECED539A18D57 /* PHGPostHogIntegration.m in Sources */, - BF8C8735F5CF80C193F5AEDF /* PHGAliasPayload.m in Sources */, - BF8C8374E0D8F97469715282 /* PHGPayload.m in Sources */, - BF8C86ED787477703D8BCD6C /* PHGScreenPayload.m in Sources */, - BF8C8334DB2564A7CFA7A87A /* PHGPayloadManager.m in Sources */, - BF8C87E4B70CA478721EB11D /* PHGCapturePayload.m in Sources */, - BF8C8331F9BAC4B30CAAC061 /* PHGIdentifyPayload.m in Sources */, - BF8C8862C12DE007F3494399 /* PHGContext.m in Sources */, - BF8C88B5A93AE29953A01E65 /* PHGMiddleware.m in Sources */, + 690FF0F52AF0F06100A0B06B /* PostHogSDKTest.swift in Sources */, + 690FF0E12AEFC59100A0B06B /* PostHogFileBackedQueueTest.swift in Sources */, + 3A62647529CB0168007E8C07 /* TestPostHog.swift in Sources */, + 3A62646A29C9E385007E8C07 /* MockPostHogServer.swift in Sources */, + 690FF0BB2AEF8B8200A0B06B /* PostHogContextTest.swift in Sources */, + 690FF0E32AEFD12900A0B06B /* PostHogConfigTest.swift in Sources */, + 3A62647129CAF67B007E8C07 /* PostHogSessionManagerTest.swift in Sources */, + 690FF0DF2AEFBC5700A0B06B /* PostHogLegacyQueueTest.swift in Sources */, + 690FF0BD2AEF93F400A0B06B /* PostHogFeatureFlagsTest.swift in Sources */, + 690FF0E92AEFD3BD00A0B06B /* PostHogQueueTest.swift in Sources */, + 3AE3FB4B2993A68500AFFC18 /* PostHogStorageTest.swift in Sources */, + 3A580B4329E489D000C5C6F3 /* URLSession+body.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - EADEB8661DECD0EF005322DA /* Sources */ = { + 69278D2E2AE6BC7100BB541A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - EADEB8F51DECD335005322DA /* CryptoTest.swift in Sources */, - EADEB8F21DECD335005322DA /* UserDefaultsStorageTest.swift in Sources */, - EAA542771EB4035400945DA7 /* CapturingTests.swift in Sources */, - EADEB8F41DECD335005322DA /* ContextTest.swift in Sources */, - A31958EF2385AC3A00A47EFA /* SerializationTests.m in Sources */, - F2A54FB028772F97006C53FF /* FeatureFlagTests.swift in Sources */, - EADEB8EE1DECD335005322DA /* HTTPClientTest.swift in Sources */, - EADEB8F31DECD335005322DA /* PostHogTests.swift in Sources */, - EADEB8F11DECD335005322DA /* FileStorageTest.swift in Sources */, - F61EB4AF1F996DEF0038C8C0 /* PostHogUtilTests.swift in Sources */, - EAA542801EB4382100945DA7 /* StoreKitCapturerTests.swift in Sources */, - EA8F09741E24C5C600B8B93F /* MiddlewareTests.swift in Sources */, - EADEB8EF1DECD335005322DA /* NSData+PHGGUNZIPP.m in Sources */, - 6EEC1C712017EA370089C478 /* EndToEndTests.swift in Sources */, - EADEB8F01DECD335005322DA /* TestUtils.swift in Sources */, + 69278D3C2AE6BC7100BB541A /* ViewController.m in Sources */, + 69278D362AE6BC7100BB541A /* AppDelegate.m in Sources */, + 69278D472AE6BC7200BB541A /* main.m in Sources */, + 69278D392AE6BC7100BB541A /* SceneDelegate.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 9D8CE58C23EE014E00197D0C /* PBXTargetDependency */ = { + 3A81BEB1297704F400A6908D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 3AC745B4296D6FE60025C109 /* PostHog */; + targetProxy = 3A81BEB0297704F400A6908D /* PBXContainerItemProxy */; + }; + 3AA34D06296D9523003398F4 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = EADEB85A1DECD080005322DA /* PostHog */; - targetProxy = 9D8CE58D23EE014E00197D0C /* PBXContainerItemProxy */; + target = 3AA34CF6296D951A003398F4 /* PostHogExample */; + targetProxy = 3AA34D05296D9523003398F4 /* PBXContainerItemProxy */; }; - EADEB8711DECD0EF005322DA /* PBXTargetDependency */ = { + 3AC745C2296D6FE60025C109 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = EADEB85A1DECD080005322DA /* PostHog */; - targetProxy = EADEB8701DECD0EF005322DA /* PBXContainerItemProxy */; + target = 3AC745B4296D6FE60025C109 /* PostHog */; + targetProxy = 3AC745C1296D6FE60025C109 /* PBXContainerItemProxy */; + }; + 69278D4E2AE6BC9000BB541A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 3AC745B4296D6FE60025C109 /* PostHog */; + targetProxy = 69278D4D2AE6BC9000BB541A /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ +/* Begin PBXVariantGroup section */ + 69278D3D2AE6BC7100BB541A /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 69278D3E2AE6BC7100BB541A /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 69278D422AE6BC7200BB541A /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 69278D432AE6BC7200BB541A /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + /* Begin XCBuildConfiguration section */ - 9D8CE5A523EE014E00197D0C /* Debug */ = { + 3AA34D03296D951B003398F4 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = CCEFC40BEE77A69F2C598C9D /* Pods-PostHog-PostHogTestsTVOS.debug.xcconfig */; buildSettings = { - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; - DEVELOPMENT_TEAM = UG8G36W2HB; - INFOPLIST_FILE = PostHogTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.posthog.PostHogTests; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"PostHogExample/Preview Content\""; + DEVELOPMENT_TEAM = PNC2XCH2XP; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.posthog.PostHogExample; PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "appletvsimulator appletvos"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OBJC_BRIDGING_HEADER = "PostHogTests/PostHogTests-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - TARGETED_DEVICE_FAMILY = 3; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TVOS_DEPLOYMENT_TARGET = 13.0; }; name = Debug; }; - 9D8CE5A623EE014E00197D0C /* Release */ = { + 3AA34D04296D951B003398F4 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 13F1CD8F13E34252559F5A83 /* Pods-PostHog-PostHogTestsTVOS.release.xcconfig */; buildSettings = { - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; - DEVELOPMENT_TEAM = UG8G36W2HB; - INFOPLIST_FILE = PostHogTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.posthog.PostHogTests; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"PostHogExample/Preview Content\""; + DEVELOPMENT_TEAM = PNC2XCH2XP; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.posthog.PostHogExample; PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "appletvsimulator appletvos"; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OBJC_BRIDGING_HEADER = "PostHogTests/PostHogTests-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - TARGETED_DEVICE_FAMILY = 3; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TVOS_DEPLOYMENT_TARGET = 13.0; }; name = Release; }; - EADEB8611DECD080005322DA /* Debug */ = { + 3AC745C7296D6FE60025C109 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; @@ -866,19 +873,21 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; @@ -892,28 +901,28 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; - SWIFT_VERSION = 4.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TVOS_DEPLOYMENT_TARGET = 11.0; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; - EADEB8621DECD080005322DA /* Release */ = { + 3AC745C8296D6FE60025C109 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; @@ -929,19 +938,21 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; @@ -949,166 +960,326 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; - SWIFT_VERSION = 4.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TVOS_DEPLOYMENT_TARGET = 11.0; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; - EADEB8641DECD080005322DA /* Debug */ = { + 3AC745CA296D6FE60025C109 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 12D0B9751E2B4493AC98D46C /* Pods-PostHog.debug.xcconfig */; buildSettings = { - APPLICATION_EXTENSION_API_ONLY = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; + BUILD_LIBRARY_FOR_DISTRIBUTION = YES; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = UG8G36W2HB; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = PostHog/Info.plist; + EXCLUDED_ARCHS = ""; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = com.posthog.PostHog; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = iphoneos; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphonesimulator iphoneos appletvos appletvsimulator"; - SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = "1,2,3"; - TVOS_DEPLOYMENT_TARGET = 11.0; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3,4"; + TVOS_DEPLOYMENT_TARGET = 13.0; + WATCHOS_DEPLOYMENT_TARGET = 6.0; }; name = Debug; }; - EADEB8651DECD080005322DA /* Release */ = { + 3AC745CB296D6FE60025C109 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = ECEED67EB700C2EFC866D7E0 /* Pods-PostHog.release.xcconfig */; buildSettings = { - APPLICATION_EXTENSION_API_ONLY = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CODE_SIGN_IDENTITY = ""; + BUILD_LIBRARY_FOR_DISTRIBUTION = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = UG8G36W2HB; + DEVELOPMENT_TEAM = PNC2XCH2XP; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = PostHog/Info.plist; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; PRODUCT_BUNDLE_IDENTIFIER = com.posthog.PostHog; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = iphoneos; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphonesimulator iphoneos appletvos appletvsimulator"; - SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = "1,2,3"; - TVOS_DEPLOYMENT_TARGET = 11.0; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3,4"; + TVOS_DEPLOYMENT_TARGET = 13.0; + WATCHOS_DEPLOYMENT_TARGET = 6.0; }; name = Release; }; - EADEB8721DECD0EF005322DA /* Debug */ = { + 3AC745CD296D6FE60025C109 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D5D6B45A7B89238D0787DFF6 /* Pods-PostHog-PostHogTests.debug.xcconfig */; buildSettings = { - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; - DEVELOPMENT_TEAM = UG8G36W2HB; - INFOPLIST_FILE = PostHogTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = PNC2XCH2XP; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.posthog.PostHogTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OBJC_BRIDGING_HEADER = "PostHogTests/PostHogTests-Bridging-Header.h"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TVOS_DEPLOYMENT_TARGET = 13.0; }; name = Debug; }; - EADEB8731DECD0EF005322DA /* Release */ = { + 3AC745CE296D6FE60025C109 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 27F9199C916C04653B965505 /* Pods-PostHog-PostHogTests.release.xcconfig */; buildSettings = { - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; - DEVELOPMENT_TEAM = UG8G36W2HB; - INFOPLIST_FILE = PostHogTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = PNC2XCH2XP; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.posthog.PostHogTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OBJC_BRIDGING_HEADER = "PostHogTests/PostHogTests-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TVOS_DEPLOYMENT_TARGET = 13.0; + }; + name = Release; + }; + 69278D492AE6BC7200BB541A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = PNC2XCH2XP; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = PostHogObjCExample/Info.plist; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.posthog.PostHogObjCExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 69278D4A2AE6BC7200BB541A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = PNC2XCH2XP; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = PostHogObjCExample/Info.plist; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.posthog.PostHogObjCExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 9D8CE5A423EE014E00197D0C /* Build configuration list for PBXNativeTarget "PostHogTestsTVOS" */ = { + 3AA34D02296D951B003398F4 /* Build configuration list for PBXNativeTarget "PostHogExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3AA34D03296D951B003398F4 /* Debug */, + 3AA34D04296D951B003398F4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 3AC745AF296D6FE60025C109 /* Build configuration list for PBXProject "PostHog" */ = { isa = XCConfigurationList; buildConfigurations = ( - 9D8CE5A523EE014E00197D0C /* Debug */, - 9D8CE5A623EE014E00197D0C /* Release */, + 3AC745C7296D6FE60025C109 /* Debug */, + 3AC745C8296D6FE60025C109 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - EADEB8551DECD080005322DA /* Build configuration list for PBXProject "PostHog" */ = { + 3AC745C9296D6FE60025C109 /* Build configuration list for PBXNativeTarget "PostHog" */ = { isa = XCConfigurationList; buildConfigurations = ( - EADEB8611DECD080005322DA /* Debug */, - EADEB8621DECD080005322DA /* Release */, + 3AC745CA296D6FE60025C109 /* Debug */, + 3AC745CB296D6FE60025C109 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - EADEB8631DECD080005322DA /* Build configuration list for PBXNativeTarget "PostHog" */ = { + 3AC745CC296D6FE60025C109 /* Build configuration list for PBXNativeTarget "PostHogTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - EADEB8641DECD080005322DA /* Debug */, - EADEB8651DECD080005322DA /* Release */, + 3AC745CD296D6FE60025C109 /* Debug */, + 3AC745CE296D6FE60025C109 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - EADEB8741DECD0EF005322DA /* Build configuration list for PBXNativeTarget "PostHogTests" */ = { + 69278D482AE6BC7200BB541A /* Build configuration list for PBXNativeTarget "PostHogObjCExample" */ = { isa = XCConfigurationList; buildConfigurations = ( - EADEB8721DECD0EF005322DA /* Debug */, - EADEB8731DECD0EF005322DA /* Release */, + 69278D492AE6BC7200BB541A /* Debug */, + 69278D4A2AE6BC7200BB541A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 3A580B3D29E481F200C5C6F3 /* XCRemoteSwiftPackageReference "OHHTTPStubs" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/AliSoftware/OHHTTPStubs"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 9.0.0; + }; + }; + 3A62646529C9E36B007E8C07 /* XCRemoteSwiftPackageReference "Shock" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/justeat/Shock"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 6.0.0; + }; + }; + 3A867B6E29C1DF73009D0852 /* XCRemoteSwiftPackageReference "Quick" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Quick/Quick.git"; + requirement = { + branch = main; + kind = branch; + }; + }; + 3A867B7129C1DFEF009D0852 /* XCRemoteSwiftPackageReference "Nimble" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Quick/Nimble.git"; + requirement = { + branch = main; + kind = branch; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 690FF0EA2AEFF22F00A0B06B /* Quick */ = { + isa = XCSwiftPackageProductDependency; + package = 3A867B6E29C1DF73009D0852 /* XCRemoteSwiftPackageReference "Quick" */; + productName = Quick; + }; + 690FF0EC2AEFF23300A0B06B /* Nimble */ = { + isa = XCSwiftPackageProductDependency; + package = 3A867B7129C1DFEF009D0852 /* XCRemoteSwiftPackageReference "Nimble" */; + productName = Nimble; + }; + 690FF0EE2AEFF23D00A0B06B /* OHHTTPStubsSwift */ = { + isa = XCSwiftPackageProductDependency; + package = 3A580B3D29E481F200C5C6F3 /* XCRemoteSwiftPackageReference "OHHTTPStubs" */; + productName = OHHTTPStubsSwift; + }; + 690FF0F02AEFF24200A0B06B /* OHHTTPStubs */ = { + isa = XCSwiftPackageProductDependency; + package = 3A580B3D29E481F200C5C6F3 /* XCRemoteSwiftPackageReference "OHHTTPStubs" */; + productName = OHHTTPStubs; + }; +/* End XCSwiftPackageProductDependency section */ }; - rootObject = EADEB8521DECD080005322DA /* Project object */; + rootObject = 3AC745AC296D6FE60025C109 /* Project object */; } diff --git a/PostHog.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/PostHog.xcodeproj/project.xcworkspace/contents.xcworkspacedata index d73e5109e..919434a62 100644 --- a/PostHog.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/PostHog.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/Examples/ManualExample/ManualExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/PostHog.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 71% rename from Examples/ManualExample/ManualExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to PostHog.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index 18d981003..08de0be8d 100644 --- a/Examples/ManualExample/ManualExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ b/PostHog.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -2,7 +2,7 @@ - IDEDidComputeMac32BitWarning - + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + diff --git a/PostHog.xcodeproj/xcshareddata/xcschemes/PostHog.xcscheme b/PostHog.xcodeproj/xcshareddata/xcschemes/PostHog.xcscheme index 20521406d..f4679242a 100644 --- a/PostHog.xcodeproj/xcshareddata/xcschemes/PostHog.xcscheme +++ b/PostHog.xcodeproj/xcshareddata/xcschemes/PostHog.xcscheme @@ -1,6 +1,6 @@ - - - - - - - - + shouldUseLaunchSchemeArgsEnv = "YES"> - - - - - - - - diff --git a/PostHog.xcodeproj/xcshareddata/xcschemes/PostHogTestsTVOS.xcscheme b/PostHog.xcodeproj/xcshareddata/xcschemes/PostHogExample.xcscheme similarity index 51% rename from PostHog.xcodeproj/xcshareddata/xcschemes/PostHogTestsTVOS.xcscheme rename to PostHog.xcodeproj/xcshareddata/xcschemes/PostHogExample.xcscheme index 100f29547..c66c1f4d3 100644 --- a/PostHog.xcodeproj/xcshareddata/xcschemes/PostHogTestsTVOS.xcscheme +++ b/PostHog.xcodeproj/xcshareddata/xcschemes/PostHogExample.xcscheme @@ -1,28 +1,33 @@ + + + + + + + shouldUseLaunchSchemeArgsEnv = "YES"> - - - - + + + + + + + + diff --git a/PostHog.xcodeproj/xcshareddata/xcschemes/PostHogExampleWatchOS Watch App.xcscheme b/PostHog.xcodeproj/xcshareddata/xcschemes/PostHogExampleWatchOS Watch App.xcscheme new file mode 100644 index 000000000..8a0b7b4d0 --- /dev/null +++ b/PostHog.xcodeproj/xcshareddata/xcschemes/PostHogExampleWatchOS Watch App.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PostHog.xcodeproj/xcshareddata/xcschemes/PostHogExampleWithPods.xcscheme b/PostHog.xcodeproj/xcshareddata/xcschemes/PostHogExampleWithPods.xcscheme new file mode 100644 index 000000000..a677b3c42 --- /dev/null +++ b/PostHog.xcodeproj/xcshareddata/xcschemes/PostHogExampleWithPods.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PostHog.xcodeproj/xcshareddata/xcschemes/PostHogExampleWithSPM.xcscheme b/PostHog.xcodeproj/xcshareddata/xcschemes/PostHogExampleWithSPM.xcscheme new file mode 100644 index 000000000..9f1491c96 --- /dev/null +++ b/PostHog.xcodeproj/xcshareddata/xcschemes/PostHogExampleWithSPM.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PostHog.xcodeproj/xcshareddata/xcschemes/PostHogObjCExample.xcscheme b/PostHog.xcodeproj/xcshareddata/xcschemes/PostHogObjCExample.xcscheme new file mode 100644 index 000000000..73e49b772 --- /dev/null +++ b/PostHog.xcodeproj/xcshareddata/xcschemes/PostHogObjCExample.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PostHog.xcodeproj/xcshareddata/xcschemes/PostHogTests.xcscheme b/PostHog.xcodeproj/xcshareddata/xcschemes/PostHogTests.xcscheme index 2706d4e0b..3ac985ef8 100644 --- a/PostHog.xcodeproj/xcshareddata/xcschemes/PostHogTests.xcscheme +++ b/PostHog.xcodeproj/xcshareddata/xcschemes/PostHogTests.xcscheme @@ -1,43 +1,35 @@ + LastUpgradeVersion = "1500" + version = "2.2"> + + + + + + - - - - - - - - - - + shouldUseLaunchSchemeArgsEnv = "YES" + shouldAutocreateTestPlan = "YES"> + skipped = "NO" + parallelizable = "YES"> diff --git a/PostHog.xcworkspace/contents.xcworkspacedata b/PostHog.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 76726e098..000000000 --- a/PostHog.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/PostHog.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/PostHog.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003..000000000 --- a/PostHog.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/PostHog/Classes/PHGAliasPayload.h b/PostHog/Classes/PHGAliasPayload.h deleted file mode 100644 index 3fd098c2c..000000000 --- a/PostHog/Classes/PHGAliasPayload.h +++ /dev/null @@ -1,15 +0,0 @@ -#import -#import "PHGPayload.h" - -NS_ASSUME_NONNULL_BEGIN - - -@interface PHGAliasPayload : PHGPayload - -@property (nonatomic, readonly) NSString *alias; - -- (instancetype)initWithAlias:(NSString *)alias; - -@end - -NS_ASSUME_NONNULL_END diff --git a/PostHog/Classes/PHGAliasPayload.m b/PostHog/Classes/PHGAliasPayload.m deleted file mode 100644 index 4d3eaf7b3..000000000 --- a/PostHog/Classes/PHGAliasPayload.m +++ /dev/null @@ -1,14 +0,0 @@ -#import "PHGAliasPayload.h" - - -@implementation PHGAliasPayload - -- (instancetype)initWithAlias:(NSString *)alias -{ - if (self = [super init]) { - _alias = [alias copy]; - } - return self; -} - -@end diff --git a/PostHog/Classes/PHGCapturePayload.h b/PostHog/Classes/PHGCapturePayload.h deleted file mode 100644 index be45121ea..000000000 --- a/PostHog/Classes/PHGCapturePayload.h +++ /dev/null @@ -1,18 +0,0 @@ -#import -#import "PHGPayload.h" - -NS_ASSUME_NONNULL_BEGIN - - -@interface PHGCapturePayload : PHGPayload - -@property (nonatomic, readonly) NSString *event; - -@property (nonatomic, readonly, nullable) NSDictionary *properties; - -- (instancetype)initWithEvent:(NSString *)event - properties:(NSDictionary *_Nullable)properties; - -@end - -NS_ASSUME_NONNULL_END diff --git a/PostHog/Classes/PHGCapturePayload.m b/PostHog/Classes/PHGCapturePayload.m deleted file mode 100644 index de68822c4..000000000 --- a/PostHog/Classes/PHGCapturePayload.m +++ /dev/null @@ -1,17 +0,0 @@ -#import "PHGCapturePayload.h" - - -@implementation PHGCapturePayload - - -- (instancetype)initWithEvent:(NSString *)event - properties:(NSDictionary *)properties -{ - if (self = [super init]) { - _event = [event copy]; - _properties = [properties copy]; - } - return self; -} - -@end diff --git a/PostHog/Classes/PHGContext.h b/PostHog/Classes/PHGContext.h deleted file mode 100644 index 2c2c2b160..000000000 --- a/PostHog/Classes/PHGContext.h +++ /dev/null @@ -1,77 +0,0 @@ -#import - -typedef NS_ENUM(NSInteger, PHGEventType) { - // Should not happen, but default state - PHGEventTypeUndefined, - // Core Capturing Methods - PHGEventTypeIdentify, - PHGEventTypeCapture, - PHGEventTypeScreen, - PHGEventTypeAlias, - PHGEventTypeGroup, - - // General utility - PHGEventTypeReset, - PHGEventTypeFlush, - - // FeatureFlags Methods - PHGEventTypeReloadFeatureFlags, - - // Remote Notification - PHGEventTypeReceivedRemoteNotification, - PHGEventTypeFailedToRegisterForRemoteNotifications, - PHGEventTypeRegisteredForRemoteNotifications, - PHGEventTypeHandleActionWithForRemoteNotification, - - // Application Lifecycle - PHGEventTypeApplicationLifecycle, - // DidFinishLaunching, - // PHGEventTypeApplicationDidEnterBackground, - // PHGEventTypeApplicationWillEnterForeground, - // PHGEventTypeApplicationWillTerminate, - // PHGEventTypeApplicationWillResignActive, - // PHGEventTypeApplicationDidBecomeActive, - - // Misc. - PHGEventTypeContinueUserActivity, - PHGEventTypeOpenURL, - -}; - -@class PHGPostHog; -@protocol PHGMutableContext; -@class PHGPayload; - - -@interface PHGContext : NSObject - -// Loopback reference to the top level PHGPostHog object. -// Not sure if it's a good idea to keep this around in the context. -// since we don't really want people to use it due to the circular -// reference and logic (Thus prefixing with underscore). But -// Right now it is required for integrations to work so I guess we'll leave it in. -@property (nonatomic, readonly, nonnull) PHGPostHog *_posthog; -@property (nonatomic, readonly) PHGEventType eventType; - -@property (nonatomic, readonly, nullable) NSString *distinctId; -@property (nonatomic, readonly, nullable) NSString *anonymousId; -@property (nonatomic, readonly, nullable) NSError *error; -@property (nonatomic, readonly, nullable) PHGPayload *payload; -@property (nonatomic, readonly) BOOL debug; - -- (instancetype _Nonnull)initWithPostHog:(PHGPostHog *_Nonnull)posthog; - -- (PHGContext *_Nonnull)modify:(void (^_Nonnull)(id _Nonnull ctx))modify; - -@end - -@protocol PHGMutableContext - -@property (nonatomic) PHGEventType eventType; -@property (nonatomic, nullable) NSString *distinctId; -@property (nonatomic, nullable) NSString *anonymousId; -@property (nonatomic, nullable) PHGPayload *payload; -@property (nonatomic, nullable) NSError *error; -@property (nonatomic) BOOL debug; - -@end diff --git a/PostHog/Classes/PHGContext.m b/PostHog/Classes/PHGContext.m deleted file mode 100644 index c2260a4e5..000000000 --- a/PostHog/Classes/PHGContext.m +++ /dev/null @@ -1,72 +0,0 @@ -#import "PHGContext.h" -#import "PHGPayload.h" - - -@interface PHGContext () - -@property (nonatomic) PHGEventType eventType; -@property (nonatomic, nullable) NSString *distinctId; -@property (nonatomic, nullable) NSString *anonymousId; -@property (nonatomic, nullable) PHGPayload *payload; -@property (nonatomic, nullable) NSError *error; -@property (nonatomic) BOOL debug; - -@end - - -@implementation PHGContext - -- (instancetype)init -{ - @throw [NSException exceptionWithName:@"Bad Initization" - reason:@"Please use initWithPostHog:" - userInfo:nil]; -} - -- (instancetype)initWithPostHog:(PHGPostHog *)posthog -{ - if (self = [super init]) { - __posthog = posthog; -// TODO: Have some other way of indicating the debug flag is on too. -// Also, for logging it'd be damn nice to implement a logging protocol -// such as CocoalumberJack and allow developers to pipe logs to wherever they want -// Of course we wouldn't us depend on it. it'd be like a soft dependency where -// posthog-ios would totally work without it but works even better with it! -#ifdef DEBUG - _debug = YES; -#endif - } - return self; -} - -- (PHGContext *_Nonnull)modify:(void (^_Nonnull)(id _Nonnull ctx))modify -{ - // We're also being a bit clever here by implementing PHGContext actually as a mutable - // object but hiding that implementation detail from consumer of the API. - // In production also instead of copying self we simply just return self - // because the net effect is the same anyways. In the end we get a lot of the benefits - // of immutable data structure without the cost of having to allocate and reallocate - // objects over and over again. - PHGContext *context = self.debug ? [self copy] : self; - modify(context); - // TODO: We could probably add some validation here that the newly modified context - // is actualy valid. For example, `eventType` should match `paylaod` class. - // or anonymousId should never be null. - return context; -} - -#pragma mark - NSCopying - -- (id)copyWithZone:(NSZone *)zone -{ - PHGContext *ctx = [[PHGContext allocWithZone:zone] initWithPostHog:self._posthog]; - ctx.eventType = self.eventType; - ctx.distinctId = self.distinctId; - ctx.anonymousId = self.anonymousId; - ctx.payload = self.payload; - ctx.error = self.error; - ctx.debug = self.debug; - return ctx; -} - -@end diff --git a/PostHog/Classes/PHGCrypto.h b/PostHog/Classes/PHGCrypto.h deleted file mode 100644 index 3410b8658..000000000 --- a/PostHog/Classes/PHGCrypto.h +++ /dev/null @@ -1,8 +0,0 @@ -#import - -@protocol PHGCrypto - -- (NSData *_Nullable)encrypt:(NSData *_Nonnull)data; -- (NSData *_Nullable)decrypt:(NSData *_Nonnull)data; - -@end diff --git a/PostHog/Classes/PHGGroupPayload.h b/PostHog/Classes/PHGGroupPayload.h deleted file mode 100644 index 96177c2c2..000000000 --- a/PostHog/Classes/PHGGroupPayload.h +++ /dev/null @@ -1,21 +0,0 @@ -#import -#import "PHGPayload.h" - -NS_ASSUME_NONNULL_BEGIN - - -@interface PHGGroupPayload : PHGPayload - -@property (nonatomic, readonly) NSString *groupType; - -@property (nonatomic, readonly) NSString *groupKey; - -@property (nonatomic, readonly, nullable) NSDictionary *properties; - -- (instancetype)initWithType:(NSString *_Nonnull)groupType - groupKey:(NSString *_Nonnull)groupKey - properties:(NSDictionary *_Nullable)properties; - -@end - -NS_ASSUME_NONNULL_END diff --git a/PostHog/Classes/PHGGroupPayload.m b/PostHog/Classes/PHGGroupPayload.m deleted file mode 100644 index a7c9e3488..000000000 --- a/PostHog/Classes/PHGGroupPayload.m +++ /dev/null @@ -1,18 +0,0 @@ -#import "PHGGroupPayload.h" - - -@implementation PHGGroupPayload - -- (instancetype)initWithType:(NSString *_Nonnull)groupType - groupKey:(NSString *_Nonnull)groupKey - properties:(NSDictionary *_Nullable)properties; -{ - if (self = [super init]) { - _groupType = [groupType copy]; - _groupKey = [groupKey copy]; - _properties = [properties copy]; - } - return self; -} - -@end diff --git a/PostHog/Classes/PHGIdentifyPayload.h b/PostHog/Classes/PHGIdentifyPayload.h deleted file mode 100644 index d0a8d028c..000000000 --- a/PostHog/Classes/PHGIdentifyPayload.h +++ /dev/null @@ -1,21 +0,0 @@ -#import -#import "PHGPayload.h" - -NS_ASSUME_NONNULL_BEGIN - - -@interface PHGIdentifyPayload : PHGPayload - -@property (nonatomic, readonly, nullable) NSString *distinctId; - -@property (nonatomic, readonly, nullable) NSString *anonymousId; - -@property (nonatomic, readonly, nullable) JSON_DICT properties; - -- (instancetype)initWithDistinctId:(NSString *)distinctId - anonymousId:(NSString *_Nullable)anonymousId - properties:(JSON_DICT _Nullable)properties; - -@end - -NS_ASSUME_NONNULL_END diff --git a/PostHog/Classes/PHGIdentifyPayload.m b/PostHog/Classes/PHGIdentifyPayload.m deleted file mode 100644 index facf13327..000000000 --- a/PostHog/Classes/PHGIdentifyPayload.m +++ /dev/null @@ -1,18 +0,0 @@ -#import "PHGIdentifyPayload.h" - - -@implementation PHGIdentifyPayload - -- (instancetype)initWithDistinctId:(NSString *)distinctId - anonymousId:(NSString *)anonymousId - properties:(NSDictionary *)properties -{ - if (self = [super init]) { - _distinctId = [distinctId copy]; - _anonymousId = [anonymousId copy]; - _properties = [properties copy]; - } - return self; -} - -@end diff --git a/PostHog/Classes/PHGIntegration.h b/PostHog/Classes/PHGIntegration.h deleted file mode 100644 index ff7aec49a..000000000 --- a/PostHog/Classes/PHGIntegration.h +++ /dev/null @@ -1,71 +0,0 @@ -#import -#import "PHGIdentifyPayload.h" -#import "PHGCapturePayload.h" -#import "PHGScreenPayload.h" -#import "PHGAliasPayload.h" -#import "PHGGroupPayload.h" - -#import "PHGIdentifyPayload.h" -#import "PHGContext.h" - -NS_ASSUME_NONNULL_BEGIN - -@protocol PHGIntegration - -@optional -// Identify will be called when the user calls either of the following: -// 1. [[PHGPostHog sharedInstance] identify:someDistinctId]; -// 2. [[PHGPostHog sharedInstance] identify:someDistinctId properties:someProperties]; -// 3. [[PHGPostHog sharedInstance] identify:someDistinctId properties:someProperties options:someOptions]; -- (void)identify:(PHGIdentifyPayload *)payload; - -// Capture will be called when the user calls either of the following: -// 1. [[PHGPostHog sharedInstance] capture:someEvent]; -// 2. [[PHGPostHog sharedInstance] capture:someEvent properties:someProperties]; -// 3. [[PHGPostHog sharedInstance] capture:someEvent properties:someProperties options:someOptions]; -- (void)capture:(PHGCapturePayload *)payload; - -// Screen will be called when the user calls either of the following: -// 1. [[PHGPostHog sharedInstance] screen:someEvent]; -// 2. [[PHGPostHog sharedInstance] screen:someEvent properties:someProperties]; -// 3. [[PHGPostHog sharedInstance] screen:someEvent properties:someProperties options:someOptions]; -- (void)screen:(PHGScreenPayload *)payload; - -// Alias will be called when the user calls either of the following: -// 1. [[PHGPostHog sharedInstance] alias:someNewAlias]; -// 2. [[PHGPostHog sharedInstance] alias:someNewAlias options:someOptions]; -- (void)alias:(PHGAliasPayload *)payload; - -- (void)group:(PHGGroupPayload *)payload; - -// Reset is invoked when the user logs out, and any data saved about the user should be cleared. -- (void)reset; - -// Flush is invoked when any queued events should be uploaded. -- (void)flush; - -// App Delegate Callbacks - -// Callbacks for notifications changes. -// ------------------------------------ -- (void)receivedRemoteNotification:(NSDictionary *)userInfo; -- (void)failedToRegisterForRemoteNotificationsWithError:(NSError *)error; -- (void)registeredForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken; -- (void)handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo; - -// Callbacks for app state changes -// ------------------------------- - -- (void)applicationDidFinishLaunching:(NSNotification *)notification; -- (void)applicationDidEnterBackground; -- (void)applicationWillEnterForeground; -- (void)applicationWillTerminate; -- (void)applicationWillResignActive; -- (void)applicationDidBecomeActive; - -- (void)continueUserActivity:(NSUserActivity *)activity; -- (void)openURL:(NSURL *)url options:(NSDictionary *)options; - -@end - -NS_ASSUME_NONNULL_END diff --git a/PostHog/Classes/PHGMiddleware.h b/PostHog/Classes/PHGMiddleware.h deleted file mode 100644 index 5cc63f0e9..000000000 --- a/PostHog/Classes/PHGMiddleware.h +++ /dev/null @@ -1,50 +0,0 @@ -#import -#import "PHGContext.h" - -typedef void (^PHGMiddlewareNext)(PHGContext *_Nullable newContext); - -@protocol PHGMiddleware -@required - -// NOTE: If you want to hold onto references of context AFTER passing it through to the next -// middleware, you should explicitly create a copy via `[context copy]` to guarantee -// that it does not get changed from underneath you because contexts can be implemented -// as mutable objects under the hood for performance optimization. -// The behavior of keeping reference to a context AFTER passing it to the next middleware -// is strictly undefined. - -// Middleware should **always** call `next`. If the intention is to explicitly filter out -// events from downstream, call `next` with `nil` as the param. -// It's ok to save next callback until a more convenient time, but it should always always be done. -// We'll probably actually add tests to sure it is so. -// TODO: Should we add error as second param to next? -- (void)context:(PHGContext *_Nonnull)context next:(PHGMiddlewareNext _Nonnull)next; - -@end - -typedef void (^PHGMiddlewareBlock)(PHGContext *_Nonnull context, PHGMiddlewareNext _Nonnull next); - - -@interface PHGBlockMiddleware : NSObject - -@property (nonnull, nonatomic, readonly) PHGMiddlewareBlock block; - -- (instancetype _Nonnull)initWithBlock:(PHGMiddlewareBlock _Nonnull)block; - -@end - - -typedef void (^RunMiddlewaresCallback)(BOOL earlyExit, NSArray> *_Nonnull remainingMiddlewares); - -// XXX TODO: Add some tests for PHGMiddlewareRunner -@interface PHGMiddlewareRunner : NSObject - -// While it is certainly technically possible to change middlewares dynamically on the fly. we're explicitly NOT -// gonna support that for now to keep things simple. If there is a real need later we'll see then. -@property (nonnull, nonatomic, readonly) NSArray> *middlewares; - -- (void)run:(PHGContext *_Nonnull)context callback:(RunMiddlewaresCallback _Nullable)callback; - -- (instancetype _Nonnull)initWithMiddlewares:(NSArray> *_Nonnull)middlewares; - -@end diff --git a/PostHog/Classes/PHGMiddleware.m b/PostHog/Classes/PHGMiddleware.m deleted file mode 100644 index 3c56c83ab..000000000 --- a/PostHog/Classes/PHGMiddleware.m +++ /dev/null @@ -1,58 +0,0 @@ -#import "PHGUtils.h" -#import "PHGMiddleware.h" - - -@implementation PHGBlockMiddleware - -- (instancetype)initWithBlock:(PHGMiddlewareBlock)block -{ - if (self = [super init]) { - _block = block; - } - return self; -} - -- (void)context:(PHGContext *)context next:(PHGMiddlewareNext)next -{ - self.block(context, next); -} - -@end - - -@implementation PHGMiddlewareRunner - -- (instancetype)initWithMiddlewares:(NSArray> *_Nonnull)middlewares -{ - if (self = [super init]) { - _middlewares = middlewares; - } - return self; -} - -- (void)run:(PHGContext *_Nonnull)context callback:(RunMiddlewaresCallback _Nullable)callback -{ - [self runMiddlewares:self.middlewares context:context callback:callback]; -} - -// TODO: Maybe rename PHGContext to PHGEvent to be a bit more clear? -// We could also use some sanity check / other types of logging here. -- (void)runMiddlewares:(NSArray> *_Nonnull)middlewares - context:(PHGContext *_Nonnull)context - callback:(RunMiddlewaresCallback _Nullable)callback -{ - BOOL earlyExit = context == nil; - if (middlewares.count == 0 || earlyExit) { - if (callback) { - callback(earlyExit, middlewares); - } - return; - } - - [middlewares[0] context:context next:^(PHGContext *_Nullable newContext) { - NSArray *remainingMiddlewares = [middlewares subarrayWithRange:NSMakeRange(1, middlewares.count - 1)]; - [self runMiddlewares:remainingMiddlewares context:newContext callback:callback]; - }]; -} - -@end diff --git a/PostHog/Classes/PHGPayload.h b/PostHog/Classes/PHGPayload.h deleted file mode 100644 index 991c35de6..000000000 --- a/PostHog/Classes/PHGPayload.h +++ /dev/null @@ -1,54 +0,0 @@ -#import -#import "PHGSerializableValue.h" - -NS_ASSUME_NONNULL_BEGIN - - -@interface PHGPayload : NSObject - -@end - - -@interface PHGApplicationLifecyclePayload : PHGPayload - -@property (nonatomic, strong) NSString *notificationName; - -// ApplicationDidFinishLaunching only -@property (nonatomic, strong, nullable) NSDictionary *launchOptions; - -@end - - -@interface PHGContinueUserActivityPayload : PHGPayload - -@property (nonatomic, strong) NSUserActivity *activity; - -@end - - -@interface PHGOpenURLPayload : PHGPayload - -@property (nonatomic, strong) NSURL *url; -@property (nonatomic, strong) NSDictionary *options; - -@end - -NS_ASSUME_NONNULL_END - - -@interface PHGRemoteNotificationPayload : PHGPayload - -// PHGEventTypeHandleActionWithForRemoteNotification -@property (nonatomic, strong, nullable) NSString *actionIdentifier; - -// PHGEventTypeHandleActionWithForRemoteNotification -// PHGEventTypeReceivedRemoteNotification -@property (nonatomic, strong, nullable) NSDictionary *userInfo; - -// PHGEventTypeFailedToRegisterForRemoteNotifications -@property (nonatomic, strong, nullable) NSError *error; - -// PHGEventTypeRegisteredForRemoteNotifications -@property (nonatomic, strong, nullable) NSData *deviceToken; - -@end diff --git a/PostHog/Classes/PHGPayload.m b/PostHog/Classes/PHGPayload.m deleted file mode 100644 index 1bb9ea6ab..000000000 --- a/PostHog/Classes/PHGPayload.m +++ /dev/null @@ -1,26 +0,0 @@ -#import "PHGPayload.h" - - -@implementation PHGPayload - -@end - - -@implementation PHGApplicationLifecyclePayload - -@end - - -@implementation PHGRemoteNotificationPayload - -@end - - -@implementation PHGContinueUserActivityPayload - -@end - - -@implementation PHGOpenURLPayload - -@end diff --git a/PostHog/Classes/PHGPayloadManager.h b/PostHog/Classes/PHGPayloadManager.h deleted file mode 100644 index 98f9da6a4..000000000 --- a/PostHog/Classes/PHGPayloadManager.h +++ /dev/null @@ -1,30 +0,0 @@ -#import -#import "PHGMiddleware.h" - -/** - * NSNotification name, that is posted after integrations are loaded. - */ -extern NSString *_Nonnull PHGPostHogIntegrationDidStart; - -@class PHGPostHog; - - -@interface PHGPayloadManager : NSObject - -- (instancetype _Nonnull)initWithPostHog:(PHGPostHog *_Nonnull)posthog; - -- (NSArray *_Nonnull)getFeatureFlags; -- (NSDictionary *_Nonnull)getFlagVariants; -- (NSDictionary *_Nonnull)getFeatureFlagPayloads; -- (NSDictionary *_Nonnull)getGroups; -- (void)saveGroup:(NSString *_Nonnull)groupType groupKey:(NSString *_Nonnull)groupKey; - -// @Deprecated - Exposing for backward API compat reasons only -- (NSString *_Nonnull)getAnonymousId; - -@end - - -@interface PHGPayloadManager (PHGMiddleware) - -@end diff --git a/PostHog/Classes/PHGPayloadManager.m b/PostHog/Classes/PHGPayloadManager.m deleted file mode 100644 index f478dfbd2..000000000 --- a/PostHog/Classes/PHGPayloadManager.m +++ /dev/null @@ -1,478 +0,0 @@ -#import -#import -#import "PHGPostHogUtils.h" -#import "PHGPostHog.h" -#import "PHGHTTPClient.h" -#import "PHGStorage.h" -#import "PHGFileStorage.h" -#import "PHGUserDefaultsStorage.h" -#import "PHGPayloadManager.h" -#import "PHGPostHogIntegration.h" - -NSString *PHGPostHogIntegrationDidStart = @"com.posthog.integration.did.start"; -static NSString *const PHGAnonymousIdKey = @"PHGAnonymousId"; -static NSString *const kPHGAnonymousIdFilename = @"posthog.anonymousId"; - - -@interface PHGPayloadManager () - -@property (nonatomic, strong) PHGPostHog *posthog; -@property (nonatomic, strong) PHGPostHogConfiguration *configuration; -@property (nonatomic, strong) dispatch_queue_t serialQueue; -@property (nonatomic, strong) NSMutableArray *messageQueue; -@property (nonatomic, strong) PHGPostHogIntegration *integration; -@property (nonatomic) volatile BOOL initialized; -@property (nonatomic, copy) NSString *cachedAnonymousId; -@property (nonatomic, strong) PHGHTTPClient *httpClient; -@property (nonatomic, strong) id userDefaultsStorage; -@property (nonatomic, strong) id fileStorage; - -@end - - -@implementation PHGPayloadManager - -- (instancetype _Nonnull)initWithPostHog:(PHGPostHog *_Nonnull)posthog -{ - PHGPostHogConfiguration *configuration = posthog.configuration; - NSCParameterAssert(configuration != nil); - - if (self = [super init]) { - self.posthog = posthog; - self.configuration = configuration; - self.serialQueue = phg_dispatch_queue_create_specific("com.posthog", DISPATCH_QUEUE_SERIAL); - self.messageQueue = [[NSMutableArray alloc] init]; - self.httpClient = [[PHGHTTPClient alloc] initWithRequestFactory:configuration.requestFactory]; - - self.userDefaultsStorage = [[PHGUserDefaultsStorage alloc] initWithDefaults:[NSUserDefaults standardUserDefaults] namespacePrefix:nil crypto:configuration.crypto]; - #if TARGET_OS_TV - self.fileStorage = [[PHGFileStorage alloc] initWithFolder:[PHGFileStorage cachesDirectoryURL] crypto:configuration.crypto]; - #else - self.fileStorage = [[PHGFileStorage alloc] initWithFolder:[PHGFileStorage applicationSupportDirectoryURL] crypto:configuration.crypto]; - #endif - - self.cachedAnonymousId = [self loadOrGenerateAnonymousID:NO]; - - self.integration = [[PHGPostHogIntegration alloc] initWithPostHog:self.posthog httpClient:self.self.httpClient fileStorage:self.fileStorage userDefaultsStorage:self.userDefaultsStorage]; - - if (self.integration.distinctId.length == 0) { - [self.integration saveDistinctId:self.cachedAnonymousId]; - } - - [[NSNotificationCenter defaultCenter] postNotificationName:PHGPostHogIntegrationDidStart object:@"PostHog" userInfo:nil]; - [self flushMessageQueue]; - - self.initialized = true; - } - return self; -} - - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -- (void)handleAppStateNotification:(NSString *)notificationName -{ - PHGLog(@"Application state change notification: %@", notificationName); - static NSDictionary *selectorMapping; - static dispatch_once_t selectorMappingOnce; - dispatch_once(&selectorMappingOnce, ^{ - selectorMapping = @{ - UIApplicationDidEnterBackgroundNotification : - NSStringFromSelector(@selector(applicationDidEnterBackground)), - UIApplicationWillTerminateNotification : - NSStringFromSelector(@selector(applicationWillTerminate)), - }; - }); - SEL selector = NSSelectorFromString(selectorMapping[notificationName]); - if (selector) { - [self callWithSelector:selector arguments:nil options:nil]; - } -} - - -#pragma mark - Public API - -- (NSString *)description -{ - return [NSString stringWithFormat:@"<%p:%@, %@>", self, [self class], [self dictionaryWithValuesForKeys:@[ @"configuration" ]]]; -} - -#pragma mark - PostHog API - -- (void)identify:(NSString *)distinctId properties:(NSDictionary *)properties options:(NSDictionary *)options -{ - NSCAssert1(distinctId.length > 0, @"distinctId (%@) must not be empty.", distinctId); - - [self saveAnonymousId:self.integration.distinctId]; - [self.integration saveDistinctId:distinctId]; - - PHGIdentifyPayload *payload = [[PHGIdentifyPayload alloc] initWithDistinctId:distinctId - anonymousId:self.cachedAnonymousId - properties:PHGCoerceDictionary(properties)]; - - [self callWithSelector:NSSelectorFromString(@"identify:") - arguments:@[ payload ] - options:options]; -} - -#pragma mark - Capture - -- (void)capture:(NSString *)event properties:(NSDictionary *)properties options:(NSDictionary *)options -{ - NSCAssert1(event.length > 0, @"event (%@) must not be empty.", event); - - PHGCapturePayload *payload = [[PHGCapturePayload alloc] initWithEvent:event - properties:PHGCoerceDictionary(properties)]; - - [self callWithSelector:NSSelectorFromString(@"capture:") - arguments:@[ payload ] - options:options]; -} - -#pragma mark - Screen - -- (void)screen:(NSString *)screenTitle properties:(NSDictionary *)properties options:(NSDictionary *)options -{ - NSCAssert1(screenTitle.length > 0, @"screen name (%@) must not be empty.", screenTitle); - - PHGScreenPayload *payload = [[PHGScreenPayload alloc] initWithName:screenTitle - properties:PHGCoerceDictionary(properties)]; - - [self callWithSelector:NSSelectorFromString(@"screen:") - arguments:@[ payload ] - options:options]; -} - -#pragma mark - Alias - -- (void)alias:(NSString *)alias options:(NSDictionary *)options -{ - PHGAliasPayload *payload = [[PHGAliasPayload alloc] initWithAlias:alias]; - - [self callWithSelector:NSSelectorFromString(@"alias:") - arguments:@[ payload ] - options:options]; -} - -#pragma mark - Group - -- (void)group:(NSString *)groupType groupKey:(NSString *)groupKey groupProperties:(NSDictionary *)properties options:(NSDictionary *)options -{ - PHGGroupPayload *payload = [[PHGGroupPayload alloc] initWithType:groupType groupKey:groupKey - properties:PHGCoerceDictionary(properties)]; - - [self callWithSelector:NSSelectorFromString(@"group:") - arguments:@[ payload ] - options:options]; -} - -#pragma mark - Feature Flags - -- (void)receivedFeatureFlags:(NSDictionary *)flags payloads:(NSDictionary *)payloads -{ - [self.integration receivedFeatureFlags:flags payloads:payloads]; -} - -- (void)reloadFeatureFlags -{ - NSMutableDictionary *payload = [[NSMutableDictionary alloc] init]; -// TODO: handle IDs - [payload setValue:[self getAnonymousId] forKey:@"$anon_distinct_id"]; - [payload setValue:self.integration.distinctId ? self.integration.distinctId : [self getAnonymousId] forKey:@"distinct_id"]; - [payload setValue:[self getGroups] forKey:@"$groups"]; - [payload setValue:self.posthog.configuration.apiKey forKey:@"api_key"]; - - NSURL *url = [self.posthog.configuration.host URLByAppendingPathComponent:@"decide"]; - NSString *absoluteUrl = [url absoluteString]; - absoluteUrl = [absoluteUrl stringByAppendingString:@"/?v=3"]; - url = [NSURL URLWithString:absoluteUrl]; - - - [self.httpClient sharedSessionUpload:payload host:url success:^(NSDictionary * _Nonnull responseDict) { - NSDictionary *flags = [responseDict objectForKey:@"featureFlags"]; - NSDictionary *flagPayloads = [responseDict objectForKey:@"featureFlagPayloads"]; - [self receivedFeatureFlags:flags payloads:flagPayloads]; - } failure:^(NSError * _Nonnull error) { - return; - }]; -} - -- (NSArray *)getFeatureFlags -{ - return [self.integration getFeatureFlags]; -} - -- (NSDictionary *)getFlagVariants -{ - return [self.integration getFeatureFlagsAndValues]; -} - -- (NSDictionary *)getFeatureFlagPayloads -{ - return [self.integration getFeatureFlagPayloads]; -} - -- (void)receivedRemoteNotification:(NSDictionary *)userInfo -{ - [self callWithSelector:_cmd arguments:@[ userInfo ] options:nil]; -} - -- (void)failedToRegisterForRemoteNotificationsWithError:(NSError *)error -{ - [self callWithSelector:_cmd arguments:@[ error ] options:nil]; -} - -- (void)registeredForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken -{ - NSParameterAssert(deviceToken != nil); - - [self callWithSelector:_cmd arguments:@[ deviceToken ] options:nil]; -} - -- (void)handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo -{ - [self callWithSelector:_cmd arguments:@[ identifier, userInfo ] options:nil]; -} - -- (void)continueUserActivity:(NSUserActivity *)activity -{ - [self callWithSelector:_cmd arguments:@[ activity ] options:nil]; -} - -- (void)openURL:(NSURL *)url options:(NSDictionary *)options -{ - [self callWithSelector:_cmd arguments:@[ url, options ] options:nil]; -} - -- (void)reset -{ - [self resetAnonymousId]; - [self callWithSelector:_cmd arguments:nil options:nil]; - [self.fileStorage resetAll]; -} - -- (void)resetAnonymousId -{ - self.cachedAnonymousId = [self loadOrGenerateAnonymousID:YES]; -} - -- (NSString *)getAnonymousId; -{ - if (self.cachedAnonymousId == nil) { - [self resetAnonymousId]; - } - return self.cachedAnonymousId; -} - -- (NSString *)loadOrGenerateAnonymousID:(BOOL)reset -{ -#if TARGET_OS_TV - NSString *anonymousId = [self.userDefaultsStorage stringForKey:PHGAnonymousIdKey]; -#else - NSString *anonymousId = [self.fileStorage stringForKey:kPHGAnonymousIdFilename]; -#endif - - if (!anonymousId || reset) { - // We've chosen to generate a UUID rather than use the UDID (deprecated in iOS 5), - // identifierForVendor (iOS6 and later, can't be changed on logout), - // or MAC address (blocked in iOS 7). - anonymousId = createUUIDString(); - PHGLog(@"New anonymousId: %@", anonymousId); -#if TARGET_OS_TV - [self.userDefaultsStorage setString:anonymousId forKey:PHGAnonymousIdKey]; -#else - [self.fileStorage setString:anonymousId forKey:kPHGAnonymousIdFilename]; -#endif - } - return anonymousId; -} - -- (void)saveAnonymousId:(NSString *)anonymousId -{ - self.cachedAnonymousId = anonymousId; -#if TARGET_OS_TV - [self.userDefaultsStorage setString:anonymousId forKey:PHGAnonymousIdKey]; -#else - [self.fileStorage setString:anonymousId forKey:kPHGAnonymousIdFilename]; -#endif -} - -- (void)saveGroup:(NSString *)groupType groupKey:(NSString *)groupKey -{ - [self.integration saveGroup:groupType groupKey:groupKey]; -} - -- (NSDictionary *)getGroups -{ - return [self.integration getGroups]; -} - -- (void)flush -{ - [self callWithSelector:_cmd arguments:nil options:nil]; -} - -#pragma mark - Private - -- (void)forwardSelector:(SEL)selector arguments:(NSArray *)arguments options:(NSDictionary *)options -{ - [self invokeIntegration:self.integration key:@"PostHog" selector:selector arguments:arguments options:options]; -} - -- (void)invokeIntegration:(PHGPostHogIntegration *)integration key:(NSString *)key selector:(SEL)selector arguments:(NSArray *)arguments options:(NSDictionary *)options -{ - NSString *eventType = NSStringFromSelector(selector); - PHGLog(@"Running: %@ with arguments %@ on integration: %@", eventType, arguments, key); - NSInvocation *invocation = [self invocationForSelector:selector arguments:arguments]; - [invocation invokeWithTarget:integration]; -} - -- (NSInvocation *)invocationForSelector:(SEL)selector arguments:(NSArray *)arguments -{ - NSMethodSignature *signature = [PHGPostHogIntegration instanceMethodSignatureForSelector:selector]; - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; - invocation.selector = selector; - for (int i = 0; i < arguments.count; i++) { - id argument = (arguments[i] == [NSNull null]) ? nil : arguments[i]; - [invocation setArgument:&argument atIndex:i + 2]; - } - return invocation; -} - -- (void)queueSelector:(SEL)selector arguments:(NSArray *)arguments options:(NSDictionary *)options -{ - NSArray *obj = @[ NSStringFromSelector(selector), arguments ?: @[], options ?: @{} ]; - PHGLog(@"Queueing: %@", obj); - [_messageQueue addObject:obj]; -} - -- (void)flushMessageQueue -{ - if (_messageQueue.count != 0) { - for (NSArray *arr in _messageQueue) - [self forwardSelector:NSSelectorFromString(arr[0]) arguments:arr[1] options:arr[2]]; - [_messageQueue removeAllObjects]; - } -} - -- (void)callWithSelector:(SEL)selector arguments:(NSArray *)arguments options:(NSDictionary *)options -{ - phg_dispatch_specific_async(_serialQueue, ^{ - if (self.initialized) { - [self flushMessageQueue]; - [self forwardSelector:selector arguments:arguments options:options]; - } else { - [self queueSelector:selector arguments:arguments options:options]; - } - }); -} - -@end - - -@interface PHGPayload (Options) -@property (readonly) NSDictionary *options; -@end - - -@implementation PHGPayload (Options) - -// Combine context into options -- (NSDictionary *)options -{ - return @{}; -} - -@end - - -@implementation PHGPayloadManager (PHGMiddleware) - -- (void)context:(PHGContext *)context next:(void (^_Nonnull)(PHGContext *_Nullable))next -{ - switch (context.eventType) { - case PHGEventTypeIdentify: { - PHGIdentifyPayload *p = (PHGIdentifyPayload *)context.payload; - NSDictionary *options; - if (p.anonymousId) { - NSMutableDictionary *mutableOptions = [[NSMutableDictionary alloc] initWithDictionary:p.options]; - mutableOptions[@"$anon_distinct_id"] = p.anonymousId; - options = [mutableOptions copy]; - } else { - options = p.options; - } - [self identify:p.distinctId properties:p.properties options:options]; - break; - } - case PHGEventTypeCapture: { - PHGCapturePayload *p = (PHGCapturePayload *)context.payload; - [self capture:p.event properties:p.properties options:p.options]; - break; - } - case PHGEventTypeScreen: { - PHGScreenPayload *p = (PHGScreenPayload *)context.payload; - [self screen:p.name properties:p.properties options:p.options]; - break; - } - case PHGEventTypeAlias: { - PHGAliasPayload *p = (PHGAliasPayload *)context.payload; - [self alias:p.alias options:p.options]; - break; - } - case PHGEventTypeGroup: { - PHGGroupPayload *p = (PHGGroupPayload *)context.payload; - [self group:p.groupType groupKey:p.groupKey groupProperties:p.properties options:p.options]; - break; - } - case PHGEventTypeReset: - [self reset]; - break; - case PHGEventTypeFlush: - [self flush]; - break; - case PHGEventTypeReceivedRemoteNotification: - [self receivedRemoteNotification: - [(PHGRemoteNotificationPayload *)context.payload userInfo]]; - break; - case PHGEventTypeFailedToRegisterForRemoteNotifications: - [self failedToRegisterForRemoteNotificationsWithError: - [(PHGRemoteNotificationPayload *)context.payload error]]; - break; - case PHGEventTypeRegisteredForRemoteNotifications: - [self registeredForRemoteNotificationsWithDeviceToken: - [(PHGRemoteNotificationPayload *)context.payload deviceToken]]; - break; - case PHGEventTypeHandleActionWithForRemoteNotification: { - PHGRemoteNotificationPayload *payload = (PHGRemoteNotificationPayload *)context.payload; - [self handleActionWithIdentifier:payload.actionIdentifier - forRemoteNotification:payload.userInfo]; - break; - } - case PHGEventTypeApplicationLifecycle: - [self handleAppStateNotification: - [(PHGApplicationLifecyclePayload *)context.payload notificationName]]; - break; - case PHGEventTypeContinueUserActivity: - [self continueUserActivity: - [(PHGContinueUserActivityPayload *)context.payload activity]]; - break; - case PHGEventTypeOpenURL: { - PHGOpenURLPayload *payload = (PHGOpenURLPayload *)context.payload; - [self openURL:payload.url options:payload.options]; - break; - } - case PHGEventTypeReloadFeatureFlags: - [self reloadFeatureFlags]; - break; - case PHGEventTypeUndefined: - NSAssert(NO, @"Received context with undefined event type %@", context); - NSLog(@"[ERROR]: Received context with undefined event type %@", context); - break; - } - next(context); -} - -@end diff --git a/PostHog/Classes/PHGPostHog.h b/PostHog/Classes/PHGPostHog.h deleted file mode 100644 index 7fd0b4100..000000000 --- a/PostHog/Classes/PHGPostHog.h +++ /dev/null @@ -1,216 +0,0 @@ -#import -#import "PHGPostHogConfiguration.h" -#import "PHGSerializableValue.h" -#import "PHGCrypto.h" - -NS_ASSUME_NONNULL_BEGIN - -/** - * This object provides an API for recording posthog. - */ -@class PHGPostHogConfiguration; - - -@interface PHGPostHog : NSObject - -/** - * Whether or not the posthog client is currently enabled. - */ -@property (nonatomic, assign, readonly) BOOL enabled; - -/** - * Used by the posthog client to configure various options. - */ -@property (nonatomic, strong, readonly) PHGPostHogConfiguration *configuration; - -/** - * Setup this posthog client instance. - * - * @param configuration The configuration used to setup the client. - */ -- (instancetype)initWithConfiguration:(PHGPostHogConfiguration *)configuration; - -/** - * Setup the posthog client. - * - * @param configuration The configuration used to setup the client. - */ -+ (void)setupWithConfiguration:(PHGPostHogConfiguration *)configuration; - -/** - * Enabled/disables debug logging to trace your data going through the SDK. - * - * @param showDebugLogs `YES` to enable logging, `NO` otherwise. `NO` by default. - */ -+ (void)debug:(BOOL)showDebugLogs; - -/** - * Returns the shared posthog client. - * - * @see -setupWithConfiguration: - */ -+ (instancetype _Nullable)sharedPostHog; - -/*! - @method - - @abstract - Associate a user with their unique ID and record properties about them. - - @param distinctId A database ID (or email address) for this user. If you don't have a distinctId - but want to record properties, you should pass nil. - - @param properties A dictionary of properties you know about the user. Things like: email, name, plan, etc. - - @param options A dictionary of options, such as the `@"anonymousId"` key. If no anonymous ID is specified one will be generated for you. - - @discussion - When you learn more about who your user is, you can record that information with identify. - - */ -- (void)identify:(NSString *)distinctId properties:(SERIALIZABLE_DICT _Nullable)properties options:(SERIALIZABLE_DICT _Nullable)options; -- (void)identify:(NSString *)distinctId properties:(SERIALIZABLE_DICT _Nullable)properties; -- (void)identify:(NSString *)distinctId; - - -/*! - @method - - @abstract - Record the actions your users perform. - - @param event The name of the event you're capturing. We recommend using human-readable names - like `Played a Song` or `Updated Status`. - - @param properties A dictionary of properties for the event. If the event was 'Added to Shopping Cart', it might - have properties like price, productType, etc. - - @discussion - When a user performs an action in your app, you'll want to capture that action for later analysis. Use the event name to say what the user did, and properties to specify any interesting details of the action. - - */ -- (void)capture:(NSString *)event properties:(SERIALIZABLE_DICT _Nullable)properties; -- (void)capture:(NSString *)event; - -/*! - @method - - @abstract - Record the screens or views your users see. - - @param screenTitle The title of the screen being viewed. We recommend using human-readable names - like 'Photo Feed' or 'Completed Purchase Screen'. - - @param properties A dictionary of properties for the screen view event. If the event was 'Added to Shopping Cart', - it might have properties like price, productType, etc. - - @discussion - When a user views a screen in your app, you'll want to record that here. For some tools like Google PostHog and Flurry, screen views are treated specially, and are different from "events" kind of like "page views" on the web. For services that don't treat "screen views" specially, we map "screen" straight to "capture" with the same parameters. For example, Mixpanel doesn't treat "screen views" any differently. So a call to "screen" will be captured as a normal event in Mixpanel, but get sent to Google PostHog and Flurry as a "screen". - - */ -- (void)screen:(NSString *)screenTitle properties:(SERIALIZABLE_DICT _Nullable)properties; -- (void)screen:(NSString *)screenTitle; - -/*! - @method - - @abstract - Merge two user identities, effectively connecting two sets of user data as one. - This may not be supported by all integrations. - - @param alias The new ID you want to alias the existing ID to. The existing ID will be either the - previousId if you have called identify, or the anonymous ID. - - @discussion - When you learn more about who the group is, you can record that information with group. - - */ -- (void)alias:(NSString *)alias; - -- (void)group:(NSString *)groupType groupKey:(NSString *)groupKey; -- (void)group:(NSString *)groupType groupKey:(NSString *)groupKey properties:(SERIALIZABLE_DICT _Nullable)properties; - -- (id)getFeatureFlag:(NSString *)flagKey; -- (id)getFeatureFlag:(NSString *)flagKey options:(NSDictionary *_Nullable)options; -- (bool)isFeatureEnabled:(NSString *)flagKey; -- (bool)isFeatureEnabled:(NSString *)flagKey options:(NSDictionary *_Nullable)options; -- (void)reloadFeatureFlags; -- (NSString *)getFeatureFlagStringPayload:(NSString *)flagKey defaultValue:(NSString *)defaultValue; -- (NSInteger)getFeatureFlagIntegerPayload:(NSString *)flagKey defaultValue:(NSInteger)defaultValue; -- (double)getFeatureFlagDoublePayload:(NSString *)flagKey defaultValue:(double)defaultValue; -- (NSDictionary *)getFeatureFlagDictionaryPayload:(NSString *)flagKey defaultValue:(NSDictionary *)defaultValue; -- (NSArray *)getFeatureFlagArrayPayload:(NSString *)flagKey defaultValue:(NSArray *)defaultValue; - - -// todo: docs -- (void)receivedRemoteNotification:(NSDictionary *)userInfo; -- (void)failedToRegisterForRemoteNotificationsWithError:(NSError *)error; -- (void)registeredForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken; -- (void)handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo; -- (void)continueUserActivity:(NSUserActivity *)activity; -- (void)openURL:(NSURL *)url options:(NSDictionary *)options; - -/*! - @method - - @abstract - Trigger an upload of all queued events. - - @discussion - This is useful when you want to force all messages queued on the device to be uploaded. Please note that not all integrations - respond to this method. - */ -- (void)flush; - -/*! - @method - - @abstract - Reset any user state that is cached on the device. - - @discussion - This is useful when a user logs out and you want to clear the identity. It will clear any - properties or distinctId's cached on the device. - */ -- (void)reset; - -/*! - @method - - @abstract - Enable the sending of posthog data. Enabled by default. - - @discussion - Occasionally used in conjunction with disable user opt-out handling. - */ -- (void)enable; - - -/*! - @method - - @abstract - Completely disable the sending of any posthog data. - - @discussion - If have a way for users to actively or passively (sometimes based on location) opt-out of - posthog data collection, you can use this method to turn off all data collection. - */ -- (void)disable; - - -/** - * Version of the library. - */ -+ (NSString *)version; - -/** Returns the anonymous ID of the current user. */ -- (NSString *)getAnonymousId; - -/** Returns the configuration used to create the posthog client. */ -- (PHGPostHogConfiguration *)configuration; - - -@end - -NS_ASSUME_NONNULL_END diff --git a/PostHog/Classes/PHGPostHog.m b/PostHog/Classes/PHGPostHog.m deleted file mode 100644 index e25276d86..000000000 --- a/PostHog/Classes/PHGPostHog.m +++ /dev/null @@ -1,583 +0,0 @@ -#import -#import -#import "PHGPostHogUtils.h" -#import "PHGPostHog.h" -#import "UIViewController+PHGScreen.h" -#import "PHGStoreKitCapturer.h" -#import "PHGStorage.h" -#import "PHGMiddleware.h" -#import "PHGPayloadManager.h" -#import "PHGUtils.h" -#import "PHGPayload.h" -#import "PHGIdentifyPayload.h" -#import "PHGCapturePayload.h" -#import "PHGScreenPayload.h" -#import "PHGAliasPayload.h" -#import "PHGGroupPayload.h" - -static PHGPostHog *__sharedInstance = nil; - - -@interface PHGPostHog () - -@property (nonatomic, assign) BOOL enabled; -@property (nonatomic, strong) PHGPostHogConfiguration *configuration; -@property (nonatomic, strong) PHGStoreKitCapturer *storeKitCapturer; -@property (nonatomic, strong) PHGPayloadManager *payloadManager; -@property (nonatomic, strong) PHGMiddlewareRunner *runner; - -@end - - -@implementation PHGPostHog - -+ (void)setupWithConfiguration:(PHGPostHogConfiguration *)configuration -{ - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - __sharedInstance = [[self alloc] initWithConfiguration:configuration]; - }); -} - -- (instancetype)initWithConfiguration:(PHGPostHogConfiguration *)configuration -{ - NSCParameterAssert(configuration != nil); - - if (self = [self init]) { - self.configuration = configuration; - self.enabled = YES; - - // In swift this would not have been OK... But hey.. It's objc - // TODO: Figure out if this is really the best way to do things here. - self.payloadManager = [[PHGPayloadManager alloc] initWithPostHog:self]; - - self.runner = [[PHGMiddlewareRunner alloc] initWithMiddlewares: - [configuration.middlewares ?: @[] arrayByAddingObject:self.payloadManager]]; - - // Attach to application state change hooks - NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; - - // Pass through for application state change events - id application = configuration.application; - if (application) { - for (NSString *name in @[ UIApplicationDidEnterBackgroundNotification, - UIApplicationDidFinishLaunchingNotification, - UIApplicationWillEnterForegroundNotification, - UIApplicationWillTerminateNotification, - UIApplicationWillResignActiveNotification, - UIApplicationDidBecomeActiveNotification ]) { - [nc addObserver:self selector:@selector(handleAppStateNotification:) name:name object:application]; - } - } - - if (configuration.recordScreenViews) { - [UIViewController phg_swizzleViewDidAppear]; - } - if (configuration.captureInAppPurchases) { - _storeKitCapturer = [PHGStoreKitCapturer captureTransactionsForPostHog:self]; - } - -#if !TARGET_OS_TV - if (configuration.capturePushNotifications && configuration.launchOptions) { - NSDictionary *remoteNotification = configuration.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]; - if (remoteNotification) { - [self capturePushNotification:remoteNotification fromLaunch:YES]; - } - } -#endif - - if (configuration.preloadFeatureFlags) { - [self reloadFeatureFlags]; - } - } - return self; -} - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -#pragma mark - - -NSString *const PHGVersionKey = @"PHGVersionKey"; -NSString *const PHGBuildKeyV1 = @"PHGBuildKey"; -NSString *const PHGBuildKeyV2 = @"PHGBuildKeyV2"; - -- (void)handleAppStateNotification:(NSNotification *)note -{ - PHGApplicationLifecyclePayload *payload = [[PHGApplicationLifecyclePayload alloc] init]; - payload.notificationName = note.name; - [self run:PHGEventTypeApplicationLifecycle payload:payload]; - - if ([note.name isEqualToString:UIApplicationDidFinishLaunchingNotification]) { - [self _applicationDidFinishLaunchingWithOptions:note.userInfo]; - } else if ([note.name isEqualToString:UIApplicationWillEnterForegroundNotification]) { - [self _applicationWillEnterForeground]; - } else if ([note.name isEqualToString: UIApplicationDidEnterBackgroundNotification]) { - [self _applicationDidEnterBackground]; - } -} - -- (void)_applicationDidFinishLaunchingWithOptions:(NSDictionary *)launchOptions -{ - if (!self.configuration.captureApplicationLifecycleEvents) { - return; - } - // Previously PHGBuildKey was stored an integer. This was incorrect because the CFBundleVersion - // can be a string. This migrates PHGBuildKey to be stored as a string. - NSInteger previousBuildV1 = [[NSUserDefaults standardUserDefaults] integerForKey:PHGBuildKeyV1]; - if (previousBuildV1) { - [[NSUserDefaults standardUserDefaults] setObject:[@(previousBuildV1) stringValue] forKey:PHGBuildKeyV2]; - [[NSUserDefaults standardUserDefaults] removeObjectForKey:PHGBuildKeyV1]; - } - - NSString *previousVersion = [[NSUserDefaults standardUserDefaults] stringForKey:PHGVersionKey]; - NSString *previousBuildV2 = [[NSUserDefaults standardUserDefaults] stringForKey:PHGBuildKeyV2]; - - NSString *currentVersion = [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"]; - NSString *currentBuild = [[NSBundle mainBundle] infoDictionary][@"CFBundleVersion"]; - - if (!previousBuildV2) { - [self capture:@"Application Installed" properties:@{ - @"version" : currentVersion ?: @"", - @"build" : currentBuild ?: @"", - }]; - } else if (![currentBuild isEqualToString:previousBuildV2]) { - [self capture:@"Application Updated" properties:@{ - @"previous_version" : previousVersion ?: @"", - @"previous_build" : previousBuildV2 ?: @"", - @"version" : currentVersion ?: @"", - @"build" : currentBuild ?: @"", - }]; - } - - [self capture:@"Application Opened" properties:@{ - @"from_background" : @NO, - @"version" : currentVersion ?: @"", - @"build" : currentBuild ?: @"", - @"referring_application" : launchOptions[UIApplicationLaunchOptionsSourceApplicationKey] ?: @"", - @"url" : launchOptions[UIApplicationLaunchOptionsURLKey] ?: @"", - }]; - - - [[NSUserDefaults standardUserDefaults] setObject:currentVersion forKey:PHGVersionKey]; - [[NSUserDefaults standardUserDefaults] setObject:currentBuild forKey:PHGBuildKeyV2]; - - [[NSUserDefaults standardUserDefaults] synchronize]; -} - -- (void)_applicationWillEnterForeground -{ - if (!self.configuration.captureApplicationLifecycleEvents) { - return; - } - [self capture:@"Application Opened" properties:@{ - @"from_background" : @YES, - }]; -} - -- (void)_applicationDidEnterBackground -{ - if (!self.configuration.captureApplicationLifecycleEvents) { - return; - } - [self capture: @"Application Backgrounded"]; -} - - -#pragma mark - Public API - -- (NSString *)description -{ - return [NSString stringWithFormat:@"<%p:%@, %@>", self, [self class], [self dictionaryWithValuesForKeys:@[ @"configuration" ]]]; -} - -#pragma mark - Identify - -- (void)identify:(NSString *)distinctId -{ - [self identify:distinctId properties:nil options:nil]; -} - -- (void)identify:(NSString *)distinctId properties:(NSDictionary *)properties -{ - [self identify:distinctId properties:properties options:nil]; -} - -- (void)identify:(NSString *)distinctId properties:(NSDictionary *)properties options:(NSDictionary *)options -{ - NSCAssert2(distinctId.length > 0 || properties.count > 0, @"either distinctId (%@) or properties (%@) must be provided.", distinctId, properties); - - NSString *anonId = [options objectForKey:@"$anon_distinct_id"]; - if (anonId == nil) { - anonId = [self getAnonymousId]; - } - - [self run:PHGEventTypeIdentify payload: - [[PHGIdentifyPayload alloc] initWithDistinctId:distinctId - anonymousId:anonId - properties:properties]]; -} - -#pragma mark - Capture - -- (void)capture:(NSString *)event -{ - [self capture:event properties:nil]; -} - -- (void)capture:(NSString *)event properties:(NSDictionary *)properties -{ - NSCAssert1(event.length > 0, @"event (%@) must not be empty.", event); - [self run:PHGEventTypeCapture payload: - [[PHGCapturePayload alloc] initWithEvent:event - properties:PHGCoerceDictionary(properties)]]; -} - -#pragma mark - Screen - -- (void)screen:(NSString *)screenTitle -{ - [self screen:screenTitle properties:nil]; -} - -- (void)screen:(NSString *)screenTitle properties:(NSDictionary *)properties -{ - NSCAssert1(screenTitle.length > 0, @"screen name (%@) must not be empty.", screenTitle); - - [self run:PHGEventTypeScreen payload: - [[PHGScreenPayload alloc] initWithName:screenTitle - properties:PHGCoerceDictionary(properties)]]; -} - -#pragma mark - Alias - -- (void)alias:(NSString *)alias -{ - [self run:PHGEventTypeAlias payload: - [[PHGAliasPayload alloc] initWithAlias:alias]]; -} - -#pragma mark - Group - -- (void)group:(NSString *_Nonnull)groupType groupKey:(NSString *_Nonnull)groupKey -{ - [self group:groupType groupKey:groupKey properties:nil]; -} - -- (void)group:(NSString *_Nonnull)groupType groupKey:(NSString *_Nonnull)groupKey properties:(NSDictionary *)properties -{ - NSDictionary *currentGroups = [self.payloadManager getGroups]; - -// TODO: set groups as super property - [self.payloadManager saveGroup:groupType groupKey:groupKey]; - - [self run:PHGEventTypeGroup payload: [[PHGGroupPayload alloc] initWithType:groupType groupKey:groupKey properties:PHGCoerceDictionary(properties)]]; - - NSString *possibleGroupKey = [currentGroups objectForKey:groupType]; - - if (![possibleGroupKey isEqualToString:groupKey]){ - [self reloadFeatureFlags]; - } -} - -- (id)getFeatureFlag:(NSString *)flagKey -{ - return [self getFeatureFlag:flagKey options:nil]; -} - -- (id)getFeatureFlag:(NSString *)flagKey options:(NSDictionary *)options -{ - NSDictionary *variants = [self.payloadManager getFlagVariants]; - id variantValue = [variants valueForKey:flagKey]; - - id send_event = [options valueForKey:@"send_event"]; - - if (send_event == nil || [send_event boolValue] != false) { - NSMutableDictionary *properties = [NSMutableDictionary dictionary]; - [properties setValue:flagKey forKey:@"$feature_flag"]; - [properties setValue:variantValue forKey:@"$feature_flag_response"]; - - - [self run:PHGEventTypeCapture payload: - [[PHGCapturePayload alloc] initWithEvent:@"$feature_flag_called" - properties:PHGCoerceDictionary(properties)]]; - } - - return variantValue; -} - -- (bool)isFeatureEnabled:(NSString *)flagKey -{ - return [self isFeatureEnabled:flagKey options:nil]; -} - -- (bool)isFeatureEnabled:(NSString *)flagKey options:(NSDictionary *)options -{ - NSDictionary *flags = [self.payloadManager getFlagVariants]; - - bool isFlagEnabled = true; - id value = [flags valueForKey:flagKey]; - - if (value != nil) { - if ([value isKindOfClass:[NSNumber class]]) { - isFlagEnabled = [value boolValue]; - } - } else { - isFlagEnabled = false; - } - - id send_event = [options valueForKey:@"send_event"]; - - if (send_event == nil || [send_event boolValue] != false) { - NSMutableDictionary *properties = [NSMutableDictionary dictionary]; - - [properties setValue:flagKey forKey:@"$feature_flag"]; - [properties setValue:@(isFlagEnabled) forKey:@"$feature_flag_response"]; - - [self run:PHGEventTypeCapture payload: - [[PHGCapturePayload alloc] initWithEvent:@"$feature_flag_called" - properties:PHGCoerceDictionary(properties)]]; - } - - return isFlagEnabled; -} - -- (NSString *)getFeatureFlagStringPayload:(NSString *)flagKey defaultValue:(NSString *)defaultValue -{ - NSDictionary *payloads = [self.payloadManager getFeatureFlagPayloads]; - id payload = [payloads valueForKey:flagKey]; - - if( payload == NULL ){ - return defaultValue; - } - - if ([payload isKindOfClass:[NSString class]]) { - return payload; - } else { - NSLog(@"[Posthog]: Could not retrieve value of type: NSString"); - return defaultValue; - } -} - -- (NSInteger)getFeatureFlagIntegerPayload:(NSString *)flagKey defaultValue:(NSInteger)defaultValue -{ - NSDictionary *payloads = [self.payloadManager getFeatureFlagPayloads]; - id payload = [payloads objectForKey:flagKey]; - - if( payload == NULL ){ - return defaultValue; - } - - if ([payload isKindOfClass:[NSNumber class]]) { - return [payload integerValue]; - } else { - NSLog(@"[Posthog]: Could not retrieve value of type: NSInteger"); - return defaultValue; - } -} - -- (double)getFeatureFlagDoublePayload:(NSString *)flagKey defaultValue:(double)defaultValue -{ - NSDictionary *payloads = [self.payloadManager getFeatureFlagPayloads]; - id payload = [payloads objectForKey:flagKey]; - - if( payload == NULL ){ - return defaultValue; - } - - if ([payload isKindOfClass:[NSNumber class]]) { - return [payload doubleValue]; - } else { - NSLog(@"[Posthog]: Could not retrieve value of type: double"); - return defaultValue; - } - -} - -- (NSDictionary *)getFeatureFlagDictionaryPayload:(NSString *)flagKey defaultValue:(NSDictionary *)defaultValue -{ - NSDictionary *payloads = [self.payloadManager getFeatureFlagPayloads]; - id payload = [payloads objectForKey:flagKey]; - - if( payload == NULL ){ - return defaultValue; - } - - if ([payload isKindOfClass:[NSDictionary class]]) { - NSDictionary* newDict = (NSDictionary*)payload; - return newDict; - } else { - NSLog(@"[Posthog]: Could not retrieve value of type: NSDictionary"); - return defaultValue; - } -} - -- (NSArray *)getFeatureFlagArrayPayload:(NSString *)flagKey defaultValue:(NSArray *)defaultValue -{ - NSDictionary *payloads = [self.payloadManager getFeatureFlagPayloads]; - id payload = [payloads objectForKey:flagKey]; - - if( payload == NULL ){ - return defaultValue; - } - - if ([payload isKindOfClass:[NSArray class]]) { - NSArray* newDict = (NSArray*)payload; - return newDict; - } else { - NSLog(@"[Posthog]: Could not retrieve value of type: NSArray"); - return defaultValue; - } -} - -- (void)reloadFeatureFlags -{ - [self run:PHGEventTypeReloadFeatureFlags payload:nil]; -} - -- (void)capturePushNotification:(NSDictionary *)properties fromLaunch:(BOOL)launch -{ - if (launch) { - [self capture:@"Push Notification Tapped" properties:properties]; - } else { - [self capture:@"Push Notification Received" properties:properties]; - } -} - -- (void)receivedRemoteNotification:(NSDictionary *)userInfo -{ - if (self.configuration.capturePushNotifications) { - [self capturePushNotification:userInfo fromLaunch:NO]; - } - PHGRemoteNotificationPayload *payload = [[PHGRemoteNotificationPayload alloc] init]; - payload.userInfo = userInfo; - [self run:PHGEventTypeReceivedRemoteNotification payload:payload]; -} - -- (void)failedToRegisterForRemoteNotificationsWithError:(NSError *)error -{ - PHGRemoteNotificationPayload *payload = [[PHGRemoteNotificationPayload alloc] init]; - payload.error = error; - [self run:PHGEventTypeFailedToRegisterForRemoteNotifications payload:payload]; -} - -- (void)registeredForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken -{ - NSParameterAssert(deviceToken != nil); - PHGRemoteNotificationPayload *payload = [[PHGRemoteNotificationPayload alloc] init]; - payload.deviceToken = deviceToken; - [self run:PHGEventTypeRegisteredForRemoteNotifications payload:payload]; -} - -- (void)handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo -{ - PHGRemoteNotificationPayload *payload = [[PHGRemoteNotificationPayload alloc] init]; - payload.actionIdentifier = identifier; - payload.userInfo = userInfo; - [self run:PHGEventTypeHandleActionWithForRemoteNotification payload:payload]; -} - -- (void)continueUserActivity:(NSUserActivity *)activity -{ - PHGContinueUserActivityPayload *payload = [[PHGContinueUserActivityPayload alloc] init]; - payload.activity = activity; - [self run:PHGEventTypeContinueUserActivity payload:payload]; - - if (!self.configuration.captureDeepLinks) { - return; - } - - if ([activity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { - NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithCapacity:activity.userInfo.count + 2]; - [properties addEntriesFromDictionary:activity.userInfo]; - properties[@"url"] = activity.webpageURL.absoluteString; - properties[@"title"] = activity.title ?: @""; - properties = [PHGUtils traverseJSON:properties - andReplaceWithFilters:self.configuration.payloadFilters]; - [self capture:@"Deep Link Opened" properties:[properties copy]]; - } -} - -- (void)openURL:(NSURL *)url options:(NSDictionary *)options -{ - PHGOpenURLPayload *payload = [[PHGOpenURLPayload alloc] init]; - payload.url = [NSURL URLWithString:[PHGUtils traverseJSON:url.absoluteString - andReplaceWithFilters:self.configuration.payloadFilters]]; - payload.options = options; - [self run:PHGEventTypeOpenURL payload:payload]; - - if (!self.configuration.captureDeepLinks) { - return; - } - - NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithCapacity:options.count + 2]; - [properties addEntriesFromDictionary:options]; - properties[@"url"] = url.absoluteString; - properties = [PHGUtils traverseJSON:properties - andReplaceWithFilters:self.configuration.payloadFilters]; - [self capture:@"Deep Link Opened" properties:[properties copy]]; -} - -- (void)reset -{ - [self run:PHGEventTypeReset payload:nil]; -} - -- (void)flush -{ - [self run:PHGEventTypeFlush payload:nil]; -} - -- (void)enable -{ - _enabled = YES; -} - -- (void)disable -{ - _enabled = NO; -} - -- (NSString *)getAnonymousId -{ - return [self.payloadManager getAnonymousId]; -} - -#pragma mark - Class Methods - -+ (instancetype)sharedPostHog -{ - NSCAssert(__sharedInstance != nil, @"library must be initialized before calling this method."); - return __sharedInstance; -} - -+ (void)debug:(BOOL)showDebugLogs -{ - PHGSetShowDebugLogs(showDebugLogs); -} - -+ (NSString *)version -{ - // this has to match the actual version, NOT what's in info.plist - // because Apple only accepts X.X.X as versions in the review process. - return @"2.1.0"; -} - -#pragma mark - Helpers - -- (void)run:(PHGEventType)eventType payload:(PHGPayload *)payload -{ - if (!self.enabled) { - return; - } - PHGContext *context = [[[PHGContext alloc] initWithPostHog:self] modify:^(id _Nonnull ctx) { - ctx.eventType = eventType; - ctx.payload = payload; - }]; - // Could probably do more things with callback later, but we don't use it yet. - [self.runner run:context callback:nil]; -} - -@end diff --git a/PostHog/Classes/PHGPostHogConfiguration.h b/PostHog/Classes/PHGPostHogConfiguration.h deleted file mode 100644 index fa0a29f2b..000000000 --- a/PostHog/Classes/PHGPostHogConfiguration.h +++ /dev/null @@ -1,191 +0,0 @@ -#import -#import - -@protocol PHGApplicationProtocol -@property (nullable, nonatomic, assign) id delegate; -- (NSUInteger)phg_beginBackgroundTaskWithName:(nullable NSString *)taskName expirationHandler:(void (^__nullable)(void))handler; -- (void)phg_endBackgroundTask:(NSUInteger)identifier; -@end - - -@interface UIApplication (PHGApplicationProtocol) -@end - -typedef NSMutableURLRequest *_Nonnull (^PHGRequestFactory)(NSURL *_Nonnull); - -@protocol PHGCrypto; -@protocol PHGMiddleware; - -/** - * This object provides a set of properties to control various policies of the posthog client. Other than `apiKey`, these properties can be changed at any time. - */ -@interface PHGPostHogConfiguration : NSObject - -/** - * Creates and returns a configuration with default settings and the given API key. - * - * @param apiKey Your team's API key. - */ -+ (_Nonnull instancetype)configurationWithApiKey:(NSString *_Nonnull)apiKey; - -/** - * Creates and returns a configuration with default settings and the given API key. - * - * @param apiKey Your team's API key. - * @param host Your API host - */ -+ (_Nonnull instancetype)configurationWithApiKey:(NSString *_Nonnull)apiKey host:(NSString *_Nonnull)host; - -/** - * Your team's API key. - * - * @see +configurationWithApiKey: - */ -@property (nonatomic, copy, readonly, nonnull) NSString *apiKey; - -/** - * Your API host. - * - * @see +configurationWithApiKey: - */ -@property (nonatomic, copy, readonly, nonnull) NSURL *host; - -/** - * Override the $lib property, used by the React Native client - */ -@property (nonatomic, copy, nonnull) NSString *libraryName; - -/** - * Override the $lib_version property, used by the React Native client - */ -@property (nonatomic, copy, nonnull) NSString *libraryVersion; - -/** - * Whether the posthog client should use location services. - * If `YES` and the host app hasn't asked for permission to use location services then the user will be presented with an alert view asking to do so. `NO` by default. - * If `YES`, please make sure to add a description for `NSLocationAlwaysUsageDescription` in your `Info.plist` explaining why your app is accessing Location APIs. - */ -@property (nonatomic, assign) BOOL shouldUseLocationServices; - -/** - * The number of queued events that the posthog client should flush at. Setting this to `1` will not queue any events and will use more battery. `20` by default. - */ -@property (nonatomic, assign) NSUInteger flushAt; - -/** - * The amount of time to wait before each tick of the flush timer. - * Smaller values will make events delivered in a more real-time manner and also use more battery. - * A value smaller than 10 seconds will seriously degrade overall performance. - * 30 seconds by default. - */ -@property (nonatomic, assign) NSTimeInterval flushInterval; - -/** - * The maximum number of items to queue before starting to drop old ones. This should be a value greater than zero, the behaviour is undefined otherwise. `1000` by default. - */ -@property (nonatomic, assign) NSUInteger maxQueueSize; - -/** - * Whether the posthog client should automatically make a capture call for application lifecycle events, such as "Application Installed", "Application Updated" and "Application Opened". - */ -@property (nonatomic, assign) BOOL captureApplicationLifecycleEvents; - - -/** - * Whether the posthog client should record bluetooth information. If `YES`, please make sure to add a description for `NSBluetoothPeripheralUsageDescription` in your `Info.plist` explaining explaining why your app is accessing Bluetooth APIs. `NO` by default. - */ -@property (nonatomic, assign) BOOL shouldUseBluetooth; - -/** - * Whether the posthog client should automatically make a screen call when a view controller is added to a view hierarchy. Because the underlying implementation uses method swizzling, we recommend initializing the posthog client as early as possible (before any screens are displayed), ideally during the Application delegate's applicationDidFinishLaunching method. - */ -@property (nonatomic, assign) BOOL recordScreenViews; - -/** - * Whether the posthog client should automatically capture in-app purchases from the App Store. - */ -@property (nonatomic, assign) BOOL captureInAppPurchases; - -/** - * Whether the posthog client should automatically capture push notifications. - */ -@property (nonatomic, assign) BOOL capturePushNotifications; - -/** - * Whether the posthog client should automatically capture deep links. You'll still need to call the continueUserActivity and openURL methods on the posthog client. - */ -@property (nonatomic, assign) BOOL captureDeepLinks; - -/** - * Whether the posthog client should include the `$device_id` property when sending events. When enabled, `UIDevice`'s `identifierForVendor` property is used. - * Changing the value of this property after initializing the client will have no effect. - * The default value is `YES`. - */ -@property (nonatomic, assign) BOOL shouldSendDeviceID; - -/** - * Whether the posthog client should load feature flags when initialized. - * Changing the value of this property after initializing the client will have no effect. - * If set to `YES` (default), `PHGPosthog` will automatically call `reloadFeatureFlags` during initialization. - * If set to `NO`, you can manually call `reloadFeatureFlags` when needed. - * The default value is `YES`. - */ -@property (nonatomic, assign) BOOL preloadFeatureFlags; - -/** - * Dictionary indicating the options the app was launched with. - */ -@property (nonatomic, strong, nullable) NSDictionary *launchOptions; - -/** - * Set a custom request factory. - */ -@property (nonatomic, strong, nullable) PHGRequestFactory requestFactory; - -/** - * Set a custom crypto - */ -@property (nonatomic, strong, nullable) id crypto; - -/** - * Set custom middlewares. Will be run before all integrations - */ -@property (nonatomic, strong, nullable) NSArray> *middlewares; - -/** - * Leave this nil for iOS extensions, otherwise set to UIApplication.sharedApplication. - */ -@property (nonatomic, strong, nullable) id application; - -/** - * A dictionary of filters to redact payloads before they are sent. - * This is an experimental feature that currently only applies to Deep Links. - * It is subject to change to allow for more flexible customizations in the future. - * - * The key of this dictionary should be a regular expression string pattern, - * and the value should be a regular expression substitution template. - * - * By default, this contains a Facebook auth token filter, configured as such: - * @code - * @"(fb\\d+://authorize#access_token=)([^ ]+)": @"$1((redacted/fb-auth-token))" - * @endcode - * - * This will replace any matching occurences to a redacted version: - * @code - * "fb123456789://authorize#access_token=secretsecretsecretsecret&some=data" - * @endcode - * - * Becomes: - * @code - * "fb123456789://authorize#access_token=((redacted/fb-auth-token))" - * @endcode - * - */ -@property (nonatomic, strong, nonnull) NSDictionary* payloadFilters; - -/** - * An optional delegate that handles NSURLSessionDelegate callbacks - */ -@property (nonatomic, strong, nullable) id httpSessionDelegate; - -@end diff --git a/PostHog/Classes/PHGPostHogConfiguration.m b/PostHog/Classes/PHGPostHogConfiguration.m deleted file mode 100644 index ab1a3ae4e..000000000 --- a/PostHog/Classes/PHGPostHogConfiguration.m +++ /dev/null @@ -1,80 +0,0 @@ -#import "PHGPostHogConfiguration.h" -#import "PHGPostHog.h" - - -@implementation UIApplication (PHGApplicationProtocol) - -- (UIBackgroundTaskIdentifier)phg_beginBackgroundTaskWithName:(nullable NSString *)taskName expirationHandler:(void (^__nullable)(void))handler -{ - return [self beginBackgroundTaskWithName:taskName expirationHandler:handler]; -} - -- (void)phg_endBackgroundTask:(UIBackgroundTaskIdentifier)identifier -{ - [self endBackgroundTask:identifier]; -} - -@end - - -@interface PHGPostHogConfiguration () - -@property (nonatomic, copy, readwrite) NSString *apiKey; -@property (nonatomic, copy, readwrite) NSURL *host; - -@end - - -@implementation PHGPostHogConfiguration - -+ (instancetype)configurationWithApiKey:(NSString *)apiKey -{ - return [[PHGPostHogConfiguration alloc] initWithApiKey:apiKey host:@"https://app.posthog.com"]; -} - -+ (instancetype)configurationWithApiKey:(NSString *)apiKey host:(NSString *)host -{ - return [[PHGPostHogConfiguration alloc] initWithApiKey:apiKey host:host]; -} - -- (instancetype)initWithApiKey:(NSString *)apiKey host:(NSString *)host -{ - if (self = [self init]) { - self.apiKey = apiKey; - self.host = [NSURL URLWithString:host]; - } - return self; -} - -- (instancetype)init -{ - if (self = [super init]) { - self.shouldUseLocationServices = NO; - self.shouldUseBluetooth = NO; - self.shouldSendDeviceID = YES; - self.flushAt = 20; - self.flushInterval = 30; - self.maxQueueSize = 1000; - self.libraryName = @"posthog-ios"; - self.libraryVersion = [PHGPostHog version]; - self.preloadFeatureFlags = YES; - self.payloadFilters = @{ - @"(fb\\d+://authorize#access_token=)([^ ]+)": @"$1((redacted/fb-auth-token))" - }; - Class applicationClass = NSClassFromString(@"UIApplication"); - if (applicationClass) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - _application = [applicationClass performSelector:NSSelectorFromString(@"sharedApplication")]; -#pragma clang diagnostic pop - } - } - return self; -} - -- (NSString *)description -{ - return [NSString stringWithFormat:@"<%p:%@, %@>", self, self.class, [self dictionaryWithValuesForKeys:@[ @"apiKey", @"shouldUseLocationServices", @"flushAt" ]]]; -} - -@end diff --git a/PostHog/Classes/PHGScreenPayload.h b/PostHog/Classes/PHGScreenPayload.h deleted file mode 100644 index ddc02a3ba..000000000 --- a/PostHog/Classes/PHGScreenPayload.h +++ /dev/null @@ -1,18 +0,0 @@ -#import -#import "PHGPayload.h" - -NS_ASSUME_NONNULL_BEGIN - - -@interface PHGScreenPayload : PHGPayload - -@property (nonatomic, readonly) NSString *name; - -@property (nonatomic, readonly, nullable) NSDictionary *properties; - -- (instancetype)initWithName:(NSString *)name - properties:(NSDictionary *_Nullable)properties; - -@end - -NS_ASSUME_NONNULL_END diff --git a/PostHog/Classes/PHGScreenPayload.m b/PostHog/Classes/PHGScreenPayload.m deleted file mode 100644 index 874c3e8df..000000000 --- a/PostHog/Classes/PHGScreenPayload.m +++ /dev/null @@ -1,16 +0,0 @@ -#import "PHGScreenPayload.h" - - -@implementation PHGScreenPayload - -- (instancetype)initWithName:(NSString *)name - properties:(NSDictionary *)properties -{ - if (self = [super init]) { - _name = [name copy]; - _properties = [properties copy]; - } - return self; -} - -@end diff --git a/PostHog/Classes/PHGSerializableValue.h b/PostHog/Classes/PHGSerializableValue.h deleted file mode 100644 index a1096903e..000000000 --- a/PostHog/Classes/PHGSerializableValue.h +++ /dev/null @@ -1,23 +0,0 @@ -#import - -/* - Acceptable dictionary values are - NSString (String); - NSNumber (Int, Float, Bool); - NSNull - NSDate => ISO8601 String - NSURL => absoluteURL String - NSArray of the above - NSDictionary of the above - */ -#define SERIALIZABLE_DICT NSDictionary * - -/* - Acceptable dictionary values are - NSString (String); - NSNumber (Int, Float, Bool); - NSNull - NSArray of the above - NSDictionary of the above - */ -#define JSON_DICT NSDictionary * diff --git a/PostHog/Info.plist b/PostHog/Info.plist deleted file mode 100644 index f69838d47..000000000 --- a/PostHog/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 2.1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSPrincipalClass - - - diff --git a/PostHog/Internal/NSData+PHGGZIP.h b/PostHog/Internal/NSData+PHGGZIP.h deleted file mode 100644 index e935212ce..000000000 --- a/PostHog/Internal/NSData+PHGGZIP.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// GZIP.h -// -// Version 1.1.1 -// -// Created by Nick Lockwood on 03/06/2012. -// Copyright (C) 2012 Charcoal Design -// -// Distributed under the permissive zlib License -// Get the latest version from here: -// -// https://github.com/nicklockwood/GZIP -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// -// 3. This notice may not be removed or altered from any source distribution. -// - - -#import - -extern void *_Nullable phg_libzOpen(void); - - -@interface NSData (PHG_GZIP) - -- (nullable NSData *)phg_gzippedData; -- (BOOL)phg_isGzippedData; - -@end diff --git a/PostHog/Internal/NSData+PHGGZIP.m b/PostHog/Internal/NSData+PHGGZIP.m deleted file mode 100644 index 5a1a66a79..000000000 --- a/PostHog/Internal/NSData+PHGGZIP.m +++ /dev/null @@ -1,107 +0,0 @@ -// -// GZIP.m -// -// Version 1.1.1 -// -// Created by Nick Lockwood on 03/06/2012. -// Copyright (C) 2012 Charcoal Design -// -// Distributed under the permissive zlib License -// Get the latest version from here: -// -// https://github.com/nicklockwood/GZIP -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// -// 3. This notice may not be removed or altered from any source distribution. -// - - -#import "NSData+PHGGZIP.h" -#import -#import - - -#pragma clang diagnostic ignored "-Wcast-qual" - -void *_Nullable phg_libzOpen() -{ - static void *libz; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - libz = dlopen("/usr/lib/libz.dylib", RTLD_LAZY); - }); - return libz; -} - - -@implementation NSData (PHG_GZIP) - -- (NSData *)phg_gzippedDataWithCompressionLevel:(float)level -{ - if (self.length == 0 || [self phg_isGzippedData]) { - return self; - } - - void *libz = phg_libzOpen(); - int (*deflateInit2_)(z_streamp, int, int, int, int, int, const char *, int) = - (int (*)(z_streamp, int, int, int, int, int, const char *, int))dlsym(libz, "deflateInit2_"); - int (*deflate)(z_streamp, int) = (int (*)(z_streamp, int))dlsym(libz, "deflate"); - int (*deflateEnd)(z_streamp) = (int (*)(z_streamp))dlsym(libz, "deflateEnd"); - - z_stream stream; - stream.zalloc = Z_NULL; - stream.zfree = Z_NULL; - stream.opaque = Z_NULL; - stream.avail_in = (uint)self.length; - stream.next_in = (Bytef *)(void *)self.bytes; - stream.total_out = 0; - stream.avail_out = 0; - - static const NSUInteger ChunkSize = 16384; - - NSMutableData *output = nil; - int compression = (level < 0.0f) ? Z_DEFAULT_COMPRESSION : (int)(roundf(level * 9)); - if (deflateInit2(&stream, compression, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY) == Z_OK) { - output = [NSMutableData dataWithLength:ChunkSize]; - while (stream.avail_out == 0) { - if (stream.total_out >= output.length) { - output.length += ChunkSize; - } - stream.next_out = (uint8_t *)output.mutableBytes + stream.total_out; - stream.avail_out = (uInt)(output.length - stream.total_out); - deflate(&stream, Z_FINISH); - } - deflateEnd(&stream); - output.length = stream.total_out; - } - - return output; -} - -- (NSData *)phg_gzippedData -{ - return [self phg_gzippedDataWithCompressionLevel:-1.0f]; -} - -- (BOOL)phg_isGzippedData -{ - const UInt8 *bytes = (const UInt8 *)self.bytes; - return (self.length >= 2 && bytes[0] == 0x1f && bytes[1] == 0x8b); -} - -@end diff --git a/PostHog/Internal/PHGAES256Crypto.h b/PostHog/Internal/PHGAES256Crypto.h deleted file mode 100644 index 352b073e0..000000000 --- a/PostHog/Internal/PHGAES256Crypto.h +++ /dev/null @@ -1,17 +0,0 @@ -#import -#import "PHGCrypto.h" - - -@interface PHGAES256Crypto : NSObject - -@property (nonatomic, readonly, nonnull) NSString *password; -@property (nonatomic, readonly, nonnull) NSData *salt; -@property (nonatomic, readonly, nonnull) NSData *iv; - -- (instancetype _Nonnull)initWithPassword:(NSString *_Nonnull)password salt:(NSData *_Nonnull)salt iv:(NSData *_Nonnull)iv; -// Convenient shorthand. Will randomly generate salt and iv. -- (instancetype _Nonnull)initWithPassword:(NSString *_Nonnull)password; - -+ (NSData *_Nonnull)randomDataOfLength:(size_t)length; - -@end diff --git a/PostHog/Internal/PHGAES256Crypto.m b/PostHog/Internal/PHGAES256Crypto.m deleted file mode 100644 index cfeeb3cd6..000000000 --- a/PostHog/Internal/PHGAES256Crypto.m +++ /dev/null @@ -1,140 +0,0 @@ -#import -#import -#import "PHGAES256Crypto.h" -#import "PHGUtils.h" - -// Implementation courtesy of http://robnapier.net/aes-commoncrypto - -static NSString *const kRNCryptManagerErrorDomain = @"com.posthog.crypto"; - -static const CCAlgorithm kAlgorithm = kCCAlgorithmAES; -static const NSUInteger kAlgorithmKeySize = kCCKeySizeAES256; -static const NSUInteger kAlgorithmBlockSize = kCCBlockSizeAES128; -static const NSUInteger kAlgorithmIVSize = kCCBlockSizeAES128; -static const NSUInteger kPBKDFSaltSize = 8; -static const NSUInteger kPBKDFRounds = 10000; // ~80ms on an iPhone 4 - - -@implementation PHGAES256Crypto - -- (instancetype)initWithPassword:(NSString *)password salt:(NSData *)salt iv:(NSData *_Nonnull)iv -{ - if (self = [super init]) { - _password = password; - _salt = salt; - _iv = iv; - } - return self; -} - -- (instancetype)initWithPassword:(NSString *)password -{ - NSData *iv = [PHGAES256Crypto randomDataOfLength:kAlgorithmIVSize]; - NSData *salt = [PHGAES256Crypto randomDataOfLength:kPBKDFSaltSize]; - return [self initWithPassword:password salt:salt iv:iv]; -} - -- (NSData *)aesKey -{ - return [[self class] AESKeyForPassword:self.password salt:self.salt]; -} - -- (NSData *)encrypt:(NSData *)data -{ - size_t outLength; - NSMutableData *cipherData = [NSMutableData dataWithLength:data.length + kAlgorithmBlockSize]; - - CCCryptorStatus - result = CCCrypt(kCCEncrypt, // operation - kAlgorithm, // Algorithm - kCCOptionPKCS7Padding, // options - self.aesKey.bytes, // key - self.aesKey.length, // keylength - self.iv.bytes, // iv - data.bytes, // dataIn - data.length, // dataInLength, - cipherData.mutableBytes, // dataOut - cipherData.length, // dataOutAvailable - &outLength); // dataOutMoved - - if (result == kCCSuccess) { - cipherData.length = outLength; - } else { - NSError *error = [NSError errorWithDomain:kRNCryptManagerErrorDomain - code:result - userInfo:nil]; - PHGLog(@"Unable to encrypt data", error); - return nil; - } - return cipherData; -} - -- (NSData *)decrypt:(NSData *)data -{ - size_t outLength; - NSMutableData *decryptedData = [NSMutableData dataWithLength:data.length + kAlgorithmBlockSize]; - - CCCryptorStatus - result = CCCrypt(kCCDecrypt, // operation - kAlgorithm, // Algorithm - kCCOptionPKCS7Padding, // options - self.aesKey.bytes, // key - self.aesKey.length, // keylength - self.iv.bytes, // iv - data.bytes, // dataIn - data.length, // dataInLength, - decryptedData.mutableBytes, // dataOut - decryptedData.length, // dataOutAvailable - &outLength); // dataOutMoved - - if (result == kCCSuccess) { - decryptedData.length = outLength; - } else { - NSError *error = [NSError errorWithDomain:kRNCryptManagerErrorDomain - code:result - userInfo:nil]; - PHGLog(@"Unable to decrypt data", error); - return nil; - } - return decryptedData; -} - -+ (NSData *)randomDataOfLength:(size_t)length -{ - NSMutableData *data = [NSMutableData dataWithLength:length]; - - int result = SecRandomCopyBytes(kSecRandomDefault, - length, - data.mutableBytes); - if (result != kCCSuccess) { - PHGLog(@"Unable to generate random bytes: %d", result); - } - - return data; -} - -// Replace this with a 10,000 hash calls if you don't have CCKeyDerivationPBKDF -+ (NSData *)AESKeyForPassword:(NSString *)password - salt:(NSData *)salt -{ - NSMutableData *derivedKey = [NSMutableData dataWithLength:kAlgorithmKeySize]; - - int result = CCKeyDerivationPBKDF(kCCPBKDF2, // algorithm - password.UTF8String, // password - [password lengthOfBytesUsingEncoding:NSUTF8StringEncoding], // passwordLength - salt.bytes, // salt - salt.length, // saltLen - kCCPRFHmacAlgSHA1, // PRF - kPBKDFRounds, // rounds - derivedKey.mutableBytes, // derivedKey - derivedKey.length); // derivedKeyLen - - // Do not log password here - if (result != kCCSuccess) { - PHGLog(@"Unable to create AES key for password: %d", result); - } - - return derivedKey; -} - -@end diff --git a/PostHog/Internal/PHGApplicationUtils.h b/PostHog/Internal/PHGApplicationUtils.h deleted file mode 100644 index 9d0751fd3..000000000 --- a/PostHog/Internal/PHGApplicationUtils.h +++ /dev/null @@ -1,11 +0,0 @@ -#import -#import - - -@interface PHGApplicationUtils : NSObject - -+ (instancetype _Nonnull) sharedInstance; -@property (nonatomic, readonly, nullable) UIApplication *sharedApplication; -@property (nonatomic, readonly, nullable) NSArray *windows; - -@end diff --git a/PostHog/Internal/PHGApplicationUtils.m b/PostHog/Internal/PHGApplicationUtils.m deleted file mode 100644 index 7b5b30b9b..000000000 --- a/PostHog/Internal/PHGApplicationUtils.m +++ /dev/null @@ -1,57 +0,0 @@ -#import "PHGApplicationUtils.h" -#import -#import - -@implementation PHGApplicationUtils - -+ (instancetype _Nonnull)sharedInstance -{ - static PHGApplicationUtils *sharedInstance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedInstance = [[PHGApplicationUtils alloc] init]; - }); - - return sharedInstance; -} - -- (UIApplication *)sharedApplication -{ - if (![UIApplication respondsToSelector:@selector(sharedApplication)]) - return nil; - - return [UIApplication performSelector:@selector(sharedApplication)]; -} - -- (NSArray *)windows -{ - UIApplication *app = [self sharedApplication]; - NSMutableArray *result = [NSMutableArray array]; - - if (@available(iOS 13.0, tvOS 13.0, *)) { - NSArray *scenes = @[]; - - if (app && [app respondsToSelector:@selector(connectedScenes)]) { - scenes = [app.connectedScenes allObjects]; - } - - for (UIScene *scene in scenes) { - if (scene.activationState == UISceneActivationStateForegroundActive && scene.delegate && - [scene.delegate respondsToSelector:@selector(window)]) { - id window = [scene.delegate performSelector:@selector(window)]; - if (window) { - [result addObject:window]; - } - } - } - } - - if ([app.delegate respondsToSelector:@selector(window)] && app.delegate.window != nil) { - [result addObject:app.delegate.window]; - } - - return result; -} - -@end - diff --git a/PostHog/Internal/PHGFileStorage.h b/PostHog/Internal/PHGFileStorage.h deleted file mode 100644 index 26f01b2b3..000000000 --- a/PostHog/Internal/PHGFileStorage.h +++ /dev/null @@ -1,17 +0,0 @@ -#import -#import "PHGStorage.h" - - -@interface PHGFileStorage : NSObject - -@property (nonatomic, strong, nullable) id crypto; - -- (instancetype _Nonnull)initWithFolder:(NSURL *_Nonnull)folderURL crypto:(id _Nullable)crypto; - -- (NSURL *_Nonnull)urlForKey:(NSString *_Nonnull)key; -- (void)resetAll; - -+ (NSURL *_Nullable)applicationSupportDirectoryURL; -+ (NSURL *_Nullable)cachesDirectoryURL; - -@end diff --git a/PostHog/Internal/PHGFileStorage.m b/PostHog/Internal/PHGFileStorage.m deleted file mode 100644 index 3e4cb91d7..000000000 --- a/PostHog/Internal/PHGFileStorage.m +++ /dev/null @@ -1,258 +0,0 @@ -#import "PHGUtils.h" -#import "PHGFileStorage.h" -#import "PHGCrypto.h" - - -@interface PHGFileStorage () - -@property (nonatomic, strong, nonnull) NSURL *folderURL; - -@end - - -@implementation PHGFileStorage - -- (instancetype)initWithFolder:(NSURL *)folderURL crypto:(id)crypto -{ - if (self = [super init]) { - _folderURL = folderURL; - _crypto = crypto; - [self createDirectoryAtURLIfNeeded:folderURL]; - return self; - } - return nil; -} - -- (void)removeKey:(NSString *)key -{ - NSURL *url = [self urlForKey:key]; - NSError *error = nil; - if (![[NSFileManager defaultManager] removeItemAtURL:url error:&error]) { - PHGLog(@"Unable to remove key %@ - error removing file at path %@", key, url); - } -} - -- (void)resetAll -{ - NSError *error = nil; - if (![[NSFileManager defaultManager] removeItemAtURL:self.folderURL error:&error]) { - PHGLog(@"ERROR: Unable to reset file storage. Path cannot be removed - %@", self.folderURL.path); - } - [self createDirectoryAtURLIfNeeded:self.folderURL]; -} - -- (void)setData:(NSData *)data forKey:(NSString *)key -{ - NSURL *url = [self urlForKey:key]; - - // a nil value was supplied, remove the storage for said key. - if (data == nil) { - [[NSFileManager defaultManager] removeItemAtURL:url error:nil]; - return; - } - - if (self.crypto) { - NSData *encryptedData = [self.crypto encrypt:data]; - [encryptedData writeToURL:url atomically:YES]; - } else { - [data writeToURL:url atomically:YES]; - } - - NSError *error = nil; - if (![url setResourceValue:@YES - forKey:NSURLIsExcludedFromBackupKey - error:&error]) { - PHGLog(@"Error excluding %@ from backup %@", [url lastPathComponent], error); - } -} - -- (NSData *)dataForKey:(NSString *)key -{ - NSURL *url = [self urlForKey:key]; - NSData *data = [NSData dataWithContentsOfURL:url]; - if (!data) { - PHGLog(@"WARNING: No data file for key %@", key); - return nil; - } - if (self.crypto) { - return [self.crypto decrypt:data]; - } - return data; -} - -- (nullable NSDictionary *)dictionaryForKey:(NSString *)key -{ - return [self jsonForKey:key]; -} - -- (void)setDictionary:(nullable NSDictionary *)dictionary forKey:(NSString *)key -{ - [self setJSON:dictionary forKey:key]; -} - -- (nullable NSArray *)arrayForKey:(NSString *)key -{ - return [self jsonForKey:key]; -} - -- (void)setArray:(nullable NSArray *)array forKey:(NSString *)key -{ - [self setJSON:array forKey:key]; -} - -- (nullable NSString *)stringForKey:(NSString *)key -{ - NSDictionary *data = [self jsonForKey:key]; - if (data) { - return data[key]; - } - return nil; -} - -- (void)setString:(nullable NSString *)string forKey:(NSString *)key -{ - [self setJSON:string forKey:key]; -} - -+ (NSURL *)applicationSupportDirectoryURL -{ - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); - NSString *storagePath = [paths firstObject]; - NSString* bundleID = [[NSBundle mainBundle] bundleIdentifier]; - - return [[NSURL fileURLWithPath:storagePath] URLByAppendingPathComponent:bundleID]; -} - -+ (NSURL *)cachesDirectoryURL -{ - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); - NSString *storagePath = [paths firstObject]; - NSString* bundleID = [[NSBundle mainBundle] bundleIdentifier]; - - return [[NSURL fileURLWithPath:storagePath] URLByAppendingPathComponent:bundleID]; -} - -- (NSURL *)urlForKey:(NSString *)key -{ - return [self.folderURL URLByAppendingPathComponent:key]; -} - -#pragma mark - Helpers - -- (id _Nullable)jsonForKey:(NSString *)key -{ - id result = nil; - - NSData *data = [self dataForKey:key]; - if (data) { - BOOL needsConversion = NO; - result = [self jsonFromData:data needsConversion:&needsConversion]; - if (needsConversion) { - [self setJSON:result forKey:key]; - // maybe a little repetitive, but we want to recreate the same path it would - // take if it weren't being converted. - data = [self dataForKey:key]; - result = [self jsonFromData:data needsConversion:&needsConversion]; - } - } - return result; -} - -- (void)setJSON:(id _Nonnull)json forKey:(NSString *)key -{ - NSDictionary *dict = nil; - - // json doesn't allow stand alone values like plist (previous storage format) does so - // we need to massage it a little. - if (json) { - if ([json isKindOfClass:[NSDictionary class]] || [json isKindOfClass:[NSArray class]]) { - dict = json; - } else { - dict = @{key: json}; - } - } - - NSData *data = [self dataFromJSON:dict]; - [self setData:data forKey:key]; -} - -- (NSData *_Nullable)dataFromJSON:(id)json -{ - if (json == nil) { - return nil; - } - - NSError *error = nil; - NSData *data = [NSJSONSerialization dataWithJSONObject:json options:0 error:&error]; - if (error) { - PHGLog(@"Unable to serialize data from json object; %@, %@", error, json); - } - return data; -} - -- (id _Nullable)jsonFromData:(NSData *_Nonnull)data needsConversion:(BOOL *)needsConversion -{ - NSError *error = nil; - id result = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; - if (error) { - // maybe it's a plist and needs to be converted. - result = [self plistFromData:data]; - if (result != nil) { - *needsConversion = YES; - } else { - PHGLog(@"Unable to parse json from data %@", error); - } - } - return result; -} - -- (void)createDirectoryAtURLIfNeeded:(NSURL *)url -{ - if (![[NSFileManager defaultManager] fileExistsAtPath:url.path - isDirectory:NULL]) { - NSError *error = nil; - if (![[NSFileManager defaultManager] createDirectoryAtPath:url.path - withIntermediateDirectories:YES - attributes:nil - error:&error]) { - PHGLog(@"error: %@", error.localizedDescription); - } - } -} - -/// Deprecated -- (NSData *_Nullable)dataFromPlist:(nonnull id)plist -{ - NSError *error = nil; - NSData *data = nil; - // Temporary just-in-case fix for issue #846; Follow-on PR to move away from plist storage. - @try { - data = [NSPropertyListSerialization dataWithPropertyList:plist - format:NSPropertyListXMLFormat_v1_0 - options:0 - error:&error]; - } @catch (NSException *e) { - PHGLog(@"Unable to serialize data from plist object; Exception: %@, plist: %@", e, plist); - } @finally { - if (error) { - PHGLog(@"Unable to serialize data from plist object; Error: %@, plist: %@", error, plist); - } - } - return data; -} - -/// Deprecated -- (id _Nullable)plistFromData:(NSData *_Nonnull)data -{ - NSError *error = nil; - id plist = [NSPropertyListSerialization propertyListWithData:data - options:0 - format:nil - error:&error]; - if (error) { - PHGLog(@"Unable to parse plist from data %@", error); - } - return plist; -} - -@end diff --git a/PostHog/Internal/PHGHTTPClient.h b/PostHog/Internal/PHGHTTPClient.h deleted file mode 100644 index 7b57a02a1..000000000 --- a/PostHog/Internal/PHGHTTPClient.h +++ /dev/null @@ -1,29 +0,0 @@ -#import -#import "PHGPostHog.h" - -NS_ASSUME_NONNULL_BEGIN - - -@interface PHGHTTPClient : NSObject - -@property (nonatomic, strong) PHGRequestFactory requestFactory; -@property (nonatomic, readonly) NSURLSession *session; -@property (nonatomic, weak) id httpSessionDelegate; - -+ (PHGRequestFactory)defaultRequestFactory; - -- (instancetype)initWithRequestFactory:(PHGRequestFactory _Nullable)requestFactory; - -/** - * This method will convert the dictionary to json, gzip it and upload the data. - * It will respond with retry = YES if the batch should be reuploaded at a later time. - * It will ask to retry for json errors and 3xx/5xx codes, and not retry for 2xx/4xx response codes. - * NOTE: You need to re-dispatch within the completionHandler onto a desired queue to avoid threading issues. - * Completion handlers are called on a dispatch queue internal to PHGHTTPClient. - */ -- (NSURLSessionUploadTask *)upload:(JSON_DICT)batch host:(NSURL *_Nonnull)host completionHandler:(void (^)(BOOL retry))completionHandler; - -- (NSURLSessionDataTask *)sharedSessionUpload:(NSDictionary *)payload host:(NSURL *)host success:(void (^)(NSDictionary *responseDict))success failure:(void(^)(NSError* error))failure; -@end - -NS_ASSUME_NONNULL_END diff --git a/PostHog/Internal/PHGHTTPClient.m b/PostHog/Internal/PHGHTTPClient.m deleted file mode 100644 index 7ea12151e..000000000 --- a/PostHog/Internal/PHGHTTPClient.m +++ /dev/null @@ -1,163 +0,0 @@ -#import "PHGHTTPClient.h" -#import "NSData+PHGGZIP.h" -#import "PHGPostHogUtils.h" - - -@implementation PHGHTTPClient - -+ (NSMutableURLRequest * (^)(NSURL *))defaultRequestFactory -{ - return ^(NSURL *url) { - return [NSMutableURLRequest requestWithURL:url]; - }; -} - -- (instancetype)initWithRequestFactory:(PHGRequestFactory)requestFactory -{ - if (self = [self init]) { - if (requestFactory == nil) { - self.requestFactory = [PHGHTTPClient defaultRequestFactory]; - } else { - self.requestFactory = requestFactory; - } - NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; - config.timeoutIntervalForRequest = 10; - - config.HTTPAdditionalHeaders = @{ - @"Accept-Encoding" : @"gzip", - @"Content-Encoding" : @"gzip", - @"Content-Type" : @"application/json", - @"User-Agent" : [NSString stringWithFormat:@"posthog-ios/%@", [PHGPostHog version]], - }; - _session = [NSURLSession sessionWithConfiguration:config delegate:_httpSessionDelegate delegateQueue:NULL]; - } - return self; -} - -- (void)dealloc -{ - [self.session finishTasksAndInvalidate]; -} - -- (NSURLSessionUploadTask *)upload:(NSDictionary *)batch host:(NSURL *)host completionHandler:(void (^)(BOOL retry))completionHandler -{ - NSURLSession *session = self.session; - NSURL *url = [host URLByAppendingPathComponent:@"batch"]; - NSMutableURLRequest *request = self.requestFactory(url); - - // This is a workaround for an IOS 8.3 bug that causes Content-Type to be incorrectly set - [request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; - - [request setHTTPMethod:@"POST"]; - - NSError *error = nil; - NSException *exception = nil; - NSData *payload = nil; - @try { - payload = [NSJSONSerialization dataWithJSONObject:batch options:0 error:&error]; - } - @catch (NSException *exc) { - exception = exc; - } - if (error || exception) { - PHGLog(@"Error serializing JSON for batch upload %@", error); - completionHandler(NO); // Don't retry this batch. - return nil; - } - NSData *gzippedPayload = [payload phg_gzippedData]; - - NSURLSessionUploadTask *task = [session uploadTaskWithRequest:request fromData:gzippedPayload completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) { - if (error) { - // Network error. Retry. - PHGLog(@"Error uploading request %@.", error); - completionHandler(YES); - return; - } - - NSInteger code = ((NSHTTPURLResponse *)response).statusCode; - if (code < 300) { - // 2xx response codes. Don't retry. - completionHandler(NO); - return; - } - if (code < 400) { - // 3xx response codes. Retry. - PHGLog(@"Server responded with unexpected HTTP code %d.", code); - completionHandler(YES); - return; - } - if (code == 429) { - // 429 response codes. Retry. - PHGLog(@"Server limited client with response code %d.", code); - completionHandler(YES); - return; - } - if (code < 500) { - // non-429 4xx response codes. Don't retry. - PHGLog(@"Server rejected payload with HTTP code %d.", code); - completionHandler(NO); - return; - } - - // 5xx response codes. Retry. - PHGLog(@"Server error with HTTP code %d.", code); - completionHandler(YES); - }]; - [task resume]; - return task; -} - -// Use shared session handler for basic network requests -- (NSURLSessionDataTask *)sharedSessionUpload:(NSDictionary *)payload host:(NSURL *)host success:(void (^)(NSDictionary *responseDict))success failure:(void(^)(NSError* error))failure -{ - NSMutableURLRequest *request = self.requestFactory(host); - [request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; - [request setHTTPMethod:@"POST"]; - - NSError *error = nil; - NSException *exception = nil; - NSData *body = nil; - @try { - body = [NSJSONSerialization dataWithJSONObject:payload options:0 error:&error]; - } - @catch (NSException *exc) { - exception = exc; - } - if (error || exception) { - PHGLog(@"Error serializing JSON for batch upload %@", error); - failure(error); // Don't retry this batch. - return nil; - } - - [request setHTTPBody:body]; - - - - NSURLSession *session = [NSURLSession sharedSession]; - NSURLSessionDataTask *task = [session dataTaskWithRequest:request - completionHandler: - ^(NSData *data, NSURLResponse *response, NSError *error) { - if (error) { - // Network error. Retry. - PHGLog(@"Error uploading request %@.", error); - failure(error); - } else { - NSInteger code = ((NSHTTPURLResponse *)response).statusCode; - if (code < 300) { - NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; - success(json); - return; - } - NSError *error = [NSError errorWithDomain:NSURLErrorDomain - code:code - userInfo:nil]; - PHGLog(@"Server responded with unexpected HTTP code.", error); - failure(error); - - } - }]; - - [task resume]; - return task; -} -@end diff --git a/PostHog/Internal/PHGMacros.h b/PostHog/Internal/PHGMacros.h deleted file mode 100644 index eb8c8dfe0..000000000 --- a/PostHog/Internal/PHGMacros.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef PHGMacros_h -#define PHGMacros_h - -#define __deprecated__(s) __attribute__((deprecated(s))) - -#define weakify(var) __weak typeof(var) __weak_##var = var; - -#define strongify(var) \ -_Pragma("clang diagnostic push") \ -_Pragma("clang diagnostic ignored \"-Wshadow\"") \ -__strong typeof(var) var = __weak_##var; \ -_Pragma("clang diagnostic pop") - -#endif /* PHGMacros_h */ diff --git a/PostHog/Internal/PHGPostHogIntegration.h b/PostHog/Internal/PHGPostHogIntegration.h deleted file mode 100644 index e48ac3220..000000000 --- a/PostHog/Internal/PHGPostHogIntegration.h +++ /dev/null @@ -1,38 +0,0 @@ -#import -#import "PHGHTTPClient.h" -#import "PHGIntegration.h" -#import "PHGStorage.h" - -@class PHGIdentifyPayload; -@class PHGCapturePayload; -@class PHGScreenPayload; -@class PHGAliasPayload; -@class PHGGroupPayload; - -NS_ASSUME_NONNULL_BEGIN - -extern NSString *const PHGPostHogDidSendRequestNotification; -extern NSString *const PHGPostHogRequestDidSucceedNotification; -extern NSString *const PHGPostHogRequestDidFailNotification; - - -@interface PHGPostHogIntegration : NSObject - -@property (nonatomic, copy, nullable) NSString *distinctId; - -- (id)initWithPostHog:(PHGPostHog *)posthog httpClient:(PHGHTTPClient *)httpClient fileStorage:(id)fileStorage userDefaultsStorage:(id)userDefaultsStorage; -- (NSDictionary *)staticContext; -- (NSDictionary *)liveContext; -- (void)saveDistinctId:(NSString *)distinctId; - -- (NSDictionary *_Nonnull)getGroups; -- (void)saveGroup:(NSString *_Nonnull)groupType groupKey:(NSString *_Nonnull)groupKey; - -- (NSArray *_Nonnull)getFeatureFlags; -- (NSDictionary *)getFeatureFlagsAndValues; -- (NSDictionary *)getFeatureFlagPayloads; -- (void)receivedFeatureFlags:(NSDictionary *)flags payloads:(NSDictionary *)payloads; - -@end - -NS_ASSUME_NONNULL_END diff --git a/PostHog/Internal/PHGPostHogIntegration.m b/PostHog/Internal/PHGPostHogIntegration.m deleted file mode 100644 index 3f8589ad9..000000000 --- a/PostHog/Internal/PHGPostHogIntegration.m +++ /dev/null @@ -1,703 +0,0 @@ -#include - -#import -#import "PHGPostHog.h" -#import "PHGPostHogUtils.h" -#import "PHGPostHogIntegration.h" -#import "PHGReachability.h" -#import "PHGHTTPClient.h" -#import "PHGStorage.h" -#import "PHGMacros.h" -#import "PHGIdentifyPayload.h" -#import "PHGCapturePayload.h" -#import "PHGScreenPayload.h" -#import "PHGAliasPayload.h" -#import "PHGGroupPayload.h" -#import "PHGApplicationUtils.h" - -#if TARGET_OS_IOS -#import -#import -#endif - -NSString *const PHGPostHogDidSendRequestNotification = @"PostHogDidSendRequest"; -NSString *const PHGPostHogRequestDidSucceedNotification = @"PostHogRequestDidSucceed"; -NSString *const PHGPostHogRequestDidFailNotification = @"PostHogRequestDidFail"; - -NSString *const PHGDistinctIdKey = @"PHGDistinctId"; -NSString *const PHGQueueKey = @"PHGQueue"; - -NSString *const kPHGDistinctIdFilename = @"posthog.distinctId"; -NSString *const kPHGQueueFilename = @"posthog.queue.plist"; - -static NSString *const PHGEnabledFeatureFlags = @"PHGEnabledFeatureFlags"; -static NSString *const kPHGEnabledFeatureFlags = @"posthog.enabledFeatureFlags"; - -static NSString *const PHGEnabledFeatureFlagPayloads = @"PHGEnabledFeatureFlagPayloads"; -static NSString *const kPHGEnabledFeatureFlagPayloads = @"posthog.enabledFeatureFlagPayloads"; - -static NSString *const PHGGroups = @"PHGGroups"; -static NSString *const kPHGGroups = @"posthog.groups"; - -static NSString *GetDeviceModel(void) -{ - size_t size; - sysctlbyname("hw.machine", NULL, &size, NULL, 0); - char result[size]; - sysctlbyname("hw.machine", result, &size, NULL, 0); - NSString *results = [NSString stringWithCString:result encoding:NSUTF8StringEncoding]; - return results; -} - -@interface PHGPostHogIntegration () - -@property (nonatomic, strong) NSMutableArray *queue; -@property (nonatomic, strong) NSDictionary *_cachedStaticContext; -@property (nonatomic, strong) NSURLSessionUploadTask *batchRequest; -@property (nonatomic, assign) UIBackgroundTaskIdentifier flushTaskID; -@property (nonatomic, strong) PHGReachability *reachability; -@property (nonatomic, strong) NSTimer *flushTimer; -@property (nonatomic, strong) dispatch_queue_t serialQueue; -@property (nonatomic, strong) dispatch_queue_t backgroundTaskQueue; -@property (nonatomic, assign) PHGPostHog *posthog; -@property (nonatomic, assign) PHGPostHogConfiguration *configuration; -@property (nonatomic, copy) NSString *referrer; -@property (nonatomic, strong) PHGHTTPClient *httpClient; -@property (nonatomic, strong) id fileStorage; -@property (nonatomic, strong) id userDefaultsStorage; - -@end - - -@implementation PHGPostHogIntegration - -- (id)initWithPostHog:(PHGPostHog *)posthog httpClient:(PHGHTTPClient *)httpClient fileStorage:(id)fileStorage userDefaultsStorage:(id)userDefaultsStorage; -{ - if (self = [super init]) { - self.posthog = posthog; - self.configuration = posthog.configuration; - self.httpClient = httpClient; - self.httpClient.httpSessionDelegate = posthog.configuration.httpSessionDelegate; - self.fileStorage = fileStorage; - self.userDefaultsStorage = userDefaultsStorage; - self.distinctId = [self getDistinctId]; - self.reachability = [PHGReachability reachabilityWithHostname:@"google.com"]; - [self.reachability startNotifier]; - self.cachedStaticContext = [self staticContext]; - self.serialQueue = phg_dispatch_queue_create_specific("com.posthog", DISPATCH_QUEUE_SERIAL); - self.backgroundTaskQueue = phg_dispatch_queue_create_specific("com.posthog.backgroundTask", DISPATCH_QUEUE_SERIAL); - self.flushTaskID = UIBackgroundTaskInvalid; - - [self dispatchBackground:^{ - // Check for previous queue data in NSUserDefaults and remove if present. - if ([[NSUserDefaults standardUserDefaults] objectForKey:PHGQueueKey]) { - [[NSUserDefaults standardUserDefaults] removeObjectForKey:PHGQueueKey]; - } - }]; - - self.flushTimer = [NSTimer timerWithTimeInterval:self.configuration.flushInterval - target:self - selector:@selector(flush) - userInfo:nil - repeats:YES]; - - [NSRunLoop.mainRunLoop addTimer:self.flushTimer - forMode:NSDefaultRunLoopMode]; - - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(updateStaticContext) - name:UIApplicationWillEnterForegroundNotification - object:nil]; - } - return self; -} - -/* - * There is an iOS bug that causes instances of the CTTelephonyNetworkInfo class to - * sometimes get notifications after they have been deallocated. - * Instead of instantiating, using, and releasing instances you * must instead retain - * and never release them to work around the bug. - * - * Ref: http://stackoverflow.com/questions/14238586/coretelephony-crash - */ - -#if TARGET_OS_IOS -static CTTelephonyNetworkInfo *_telephonyNetworkInfo; -#endif - -- (NSDictionary *)staticContext -{ - NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; - - NSMutableDictionary *infoDictionary = [[[NSBundle mainBundle] infoDictionary] mutableCopy]; - [infoDictionary addEntriesFromDictionary:[[NSBundle mainBundle] localizedInfoDictionary]]; - if (infoDictionary.count) { - dict[@"$app_name"] = infoDictionary[@"CFBundleDisplayName"] ?: @""; - dict[@"$app_version"] = infoDictionary[@"CFBundleShortVersionString"] ?: @""; - dict[@"$app_build"] = infoDictionary[@"CFBundleVersion"] ?: @""; - dict[@"$app_namespace"] = [[NSBundle mainBundle] bundleIdentifier] ?: @""; - } - - UIDevice *device = [UIDevice currentDevice]; - - NSString *deviceType = nil; - switch (device.userInterfaceIdiom) { - case UIUserInterfaceIdiomUnspecified: - break; - case UIUserInterfaceIdiomPhone: - deviceType = @"Mobile"; - break; - case UIUserInterfaceIdiomPad: - deviceType = @"Tablet"; - break; - case UIUserInterfaceIdiomTV: - deviceType = @"TV"; - break; - case UIUserInterfaceIdiomCarPlay: - deviceType = @"CarPlay"; - break; - case UIUserInterfaceIdiomMac: - deviceType = @"Desktop"; - break; - } - - dict[@"$device_manufacturer"] = @"Apple"; - if (deviceType != nil) dict[@"$device_type"] = deviceType; - dict[@"$device_model"] = GetDeviceModel(); - dict[@"$device_id"] = self.configuration.shouldSendDeviceID ? [[device identifierForVendor] UUIDString] : nil; - dict[@"$device_name"] = [device model]; - - dict[@"$os_name"] = device.systemName; - dict[@"$os_version"] = device.systemVersion; - -#if TARGET_OS_IOS || TARGET_OS_TV - NSArray *appWindows = [PHGApplicationUtils.sharedInstance windows]; - if ([appWindows count] > 0) { - UIScreen *appScreen = appWindows.firstObject.screen; - if (appScreen != nil) { - dict[@"$screen_width"] = @(appScreen.bounds.size.height); - dict[@"$screen_height"] = @(appScreen.bounds.size.width); - } - } -#endif - - return dict; -} - -- (void)updateStaticContext -{ - self.cachedStaticContext = [self staticContext]; -} - -- (NSDictionary *)cachedStaticContext { - __block NSDictionary *result = nil; - weakify(self); - dispatch_sync(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ - strongify(self); - result = self._cachedStaticContext; - }); - return result; -} - -- (void)setCachedStaticContext:(NSDictionary *)cachedStaticContext { - weakify(self); - dispatch_sync(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ - strongify(self); - self._cachedStaticContext = cachedStaticContext; - }); -} - -- (NSDictionary *)liveContext -{ - NSMutableDictionary *context = [[NSMutableDictionary alloc] init]; - - context[@"$lib"] = [self configuration].libraryName; - context[@"$lib_version"] = [self configuration].libraryVersion; - - if ([NSLocale.currentLocale objectForKey:NSLocaleCountryCode]) { - context[@"$locale"] = [NSString stringWithFormat: - @"%@-%@", - [NSLocale.currentLocale objectForKey:NSLocaleLanguageCode], - [NSLocale.currentLocale objectForKey:NSLocaleCountryCode]]; - } else { - context[@"$locale"] = [NSLocale.currentLocale objectForKey:NSLocaleLanguageCode]; - } - - context[@"$timezone"] = [[NSTimeZone localTimeZone] name]; - - if (self.reachability.isReachable) { - context[@"$network_wifi"] = @(self.reachability.isReachableViaWiFi); - context[@"$network_cellular"] = @(self.reachability.isReachableViaWWAN); - } else { - context[@"$network_wifi"] = @NO; - context[@"$network_cellular"] = @NO; - } - - context[@"$groups"] = [self getGroups]; - - NSDictionary *flagsAndValues = [self getFeatureFlagsAndValues]; - - NSMutableArray *activeFlags = [NSMutableArray array]; - int n = 0; - for(id flag in flagsAndValues){ - NSString *key = [NSString stringWithFormat:@"$feature/%@", flag]; - // $active_feature_flags__x is legacy and likely not used anymore - NSString *enumeratedKey = [NSString stringWithFormat:@"$active_feature_flags__%d", n]; - - id value = [flagsAndValues valueForKey:flag]; - - context[key] = value; - context[enumeratedKey] = flag; - - // only add active feature flags - // a flag is only inactive if its a boolean: false - bool isActive = YES; - if ([value isKindOfClass:[NSNumber class]]) { - isActive = [value boolValue]; - } - if (isActive) { - [activeFlags addObject:key]; - } - - n++; - } - if ([activeFlags count] > 0) { - [context setObject:activeFlags forKey:@"$active_feature_flags"]; - } - -#if TARGET_OS_IOS - static dispatch_once_t networkInfoOnceToken; - dispatch_once(&networkInfoOnceToken, ^{ - _telephonyNetworkInfo = [[CTTelephonyNetworkInfo alloc] init]; - }); - - CTCarrier *carrier = [_telephonyNetworkInfo subscriberCellularProvider]; - if (carrier.carrierName.length) - context[@"$network_carrier"] = carrier.carrierName; -#endif - - if (self.referrer) { - context[@"$referrer"] = [self.referrer copy]; - } - - return [context copy]; -} - -- (void)dispatchBackground:(void (^)(void))block -{ - phg_dispatch_specific_async(_serialQueue, block); -} - -- (void)dispatchBackgroundAndWait:(void (^)(void))block -{ - phg_dispatch_specific_sync(_serialQueue, block); -} - -- (void)beginBackgroundTask -{ - [self endBackgroundTask]; - - phg_dispatch_specific_sync(_backgroundTaskQueue, ^{ - id application = [self.posthog configuration].application; - if (application) { - self.flushTaskID = [application phg_beginBackgroundTaskWithName:@"PostHog.Flush" - expirationHandler:^{ - [self endBackgroundTask]; - }]; - } - }); -} - -- (void)endBackgroundTask -{ - // endBackgroundTask and beginBackgroundTask can be called from main thread - // We should not dispatch to the same queue we use to flush events because it can cause deadlock - // inside @synchronized(self) block for PHGIntegrationsManager as both events queue and main queue - // attempt to call forwardSelector:arguments:options: - phg_dispatch_specific_sync(_backgroundTaskQueue, ^{ - if (self.flushTaskID != UIBackgroundTaskInvalid) { - id application = [self.posthog configuration].application; - if (application) { - [application phg_endBackgroundTask:self.flushTaskID]; - } - - self.flushTaskID = UIBackgroundTaskInvalid; - } - }); -} - -- (NSString *)description -{ - return [NSString stringWithFormat:@"<%p:%@, %@>", self, self.class, self.configuration.apiKey]; -} - -- (void)saveDistinctId:(NSString *)distinctId -{ - self.distinctId = distinctId; - -#if TARGET_OS_TV - [self.userDefaultsStorage setString:distinctId forKey:PHGDistinctIdKey]; -#else - [self.fileStorage setString:distinctId forKey:kPHGDistinctIdFilename]; -#endif -} - -#pragma mark - PostHog API - -- (void)identify:(PHGIdentifyPayload *)payload -{ - [self dispatchBackground:^{ - [self saveDistinctId:payload.distinctId]; - }]; - - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - - [dictionary setValue:@"$identify" forKey:@"event"]; - [dictionary setValue:payload.distinctId ?: payload.anonymousId ?: [self.posthog getAnonymousId] forKey:@"distinct_id"]; - [dictionary setValue:payload.properties forKey:@"$set"]; - - NSMutableDictionary *properties = [NSMutableDictionary dictionary]; - if (payload.distinctId) { - [properties setValue:payload.anonymousId ?: [self.posthog getAnonymousId] forKey:@"$anon_distinct_id"]; - } - [dictionary setValue:properties forKey:@"properties"]; - - [self enqueueAction:dictionary]; -} - -- (void)capture:(PHGCapturePayload *)payload -{ - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [dictionary setValue:self.distinctId ?: [self.posthog getAnonymousId] forKey:@"distinct_id"]; - [dictionary setValue:payload.event forKey:@"event"]; - - NSMutableDictionary *properties = [[NSMutableDictionary alloc] initWithDictionary:payload.properties copyItems:YES]; - [dictionary setValue:properties forKey:@"properties"]; - - [self enqueueAction:dictionary]; -} - -- (void)screen:(PHGScreenPayload *)payload -{ - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [dictionary setValue:self.distinctId ?: [self.posthog getAnonymousId] forKey:@"distinct_id"]; - [dictionary setValue:@"$screen" forKey:@"event"]; - - NSMutableDictionary *properties = [[NSMutableDictionary alloc] initWithDictionary:payload.properties copyItems:YES]; - [properties setValue:payload.name forKey:@"$screen_name"]; - [dictionary setValue:properties forKey:@"properties"]; - - [self enqueueAction:dictionary]; -} - -- (void)alias:(PHGAliasPayload *)payload -{ - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [dictionary setValue:@"$create_alias" forKey:@"event"]; - - NSMutableDictionary *properties = [NSMutableDictionary dictionary]; - [properties setValue:self.distinctId ?: [self.posthog getAnonymousId] forKey:@"distinct_id"]; - [properties setValue:payload.alias forKey:@"alias"]; - [dictionary setValue:properties forKey:@"properties"]; - - [self enqueueAction:dictionary]; -} - -- (void)group:(PHGGroupPayload *)payload -{ - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - [dictionary setValue:@"$groupidentify" forKey:@"event"]; - - NSMutableDictionary *properties = [NSMutableDictionary dictionary]; - [properties setValue:payload.groupType forKey:@"$group_type"]; - [properties setValue:payload.groupKey forKey:@"$group_key"]; - [properties setValue:payload.properties forKey:@"$group_set"]; - - [dictionary setValue:properties forKey:@"properties"]; - [dictionary setValue:self.distinctId ?: [self.posthog getAnonymousId] forKey:@"distinct_id"]; - [self enqueueAction:dictionary]; -} - -- (void)receivedRemoteNotification:(NSDictionary *)userInfo { - // NoOp -} - -- (void)failedToRegisterForRemoteNotificationsWithError:(NSError *)error { - // NoOp -} - -- (void)registeredForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken -{ - NSCParameterAssert(deviceToken != nil); - - const unsigned char *buffer = (const unsigned char *)[deviceToken bytes]; - if (!buffer) { - return; - } - NSMutableString *token = [NSMutableString stringWithCapacity:(deviceToken.length * 2)]; - for (NSUInteger i = 0; i < deviceToken.length; i++) { - [token appendString:[NSString stringWithFormat:@"%02lx", (unsigned long)buffer[i]]]; - } - [self.cachedStaticContext[@"device"] setObject:[token copy] forKey:@"token"]; -} - -- (void)handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo { - // NoOp -} - -- (void)continueUserActivity:(NSUserActivity *)activity -{ - if ([activity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { - self.referrer = activity.webpageURL.absoluteString; - } -} - -- (void)openURL:(NSURL *)url options:(NSDictionary *)options -{ - self.referrer = url.absoluteString; -} - -#pragma mark - Queueing - -- (void)enqueueAction:(NSMutableDictionary *)payload -{ - // attach these parts of the payload outside since they are all synchronous - // and the timestamp will be more accurate. - payload[@"timestamp"] = createISO8601FormattedString([NSDate date]); - payload[@"message_id"] = createUUIDString(); - - [self dispatchBackground:^{ - // attach distinctId and anonymousId inside the dispatch_async in case - // they've changed (see identify function) - - NSDictionary *staticContext = self.cachedStaticContext; - NSDictionary *liveContext = [self liveContext]; - - NSMutableDictionary *properties = payload[@"properties"]; - [properties addEntriesFromDictionary:staticContext]; - [properties addEntriesFromDictionary:liveContext]; - [payload setValue:[properties copy] forKey:@"properties"]; - - PHGLog(@"%@ Enqueueing action: %@", self, payload); - [self queuePayload:[payload copy]]; - }]; -} - -- (void)queuePayload:(NSDictionary *)payload -{ - @try { - // Trim the queue to maxQueueSize - 1 before we add a new element. - trimQueueItems(self.queue, self.posthog.configuration.maxQueueSize - 1); - [self.queue addObject:payload]; - [self persistQueue]; - [self flushQueueByLength]; - } - @catch (NSException *exception) { - PHGLog(@"%@ Error writing payload: %@", self, exception); - } -} - -- (void)flush -{ - [self flushWithMaxSize:self.maxBatchSize]; -} - -- (void)flushWithMaxSize:(NSUInteger)maxBatchSize -{ - [self dispatchBackground:^{ - if ([self.queue count] == 0) { - PHGLog(@"%@ No queued API calls to flush.", self); - [self endBackgroundTask]; - return; - } - if (self.batchRequest != nil) { - PHGLog(@"%@ API request already in progress, not flushing again.", self); - return; - } - - NSArray *batch; - if ([self.queue count] >= maxBatchSize) { - batch = [self.queue subarrayWithRange:NSMakeRange(0, maxBatchSize)]; - } else { - batch = [NSArray arrayWithArray:self.queue]; - } - - [self sendData:batch]; - }]; -} - -- (void)flushQueueByLength -{ - [self dispatchBackground:^{ - PHGLog(@"%@ Length is %lu.", self, (unsigned long)self.queue.count); - - if (self.batchRequest == nil && [self.queue count] >= self.configuration.flushAt) { - [self flush]; - } - }]; -} - -- (void)reset -{ - [self dispatchBackgroundAndWait:^{ -#if TARGET_OS_TV - [self.userDefaultsStorage removeKey:PHGDistinctIdKey]; -#else - [self.fileStorage removeKey:kPHGDistinctIdFilename]; -#endif - self.distinctId = nil; - }]; -} - -- (void)notifyForName:(NSString *)name userInfo:(id)userInfo -{ - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:name object:userInfo]; - PHGLog(@"sent notification %@", name); - }); -} - -- (void)sendData:(NSArray *)batch -{ - NSMutableDictionary *payload = [[NSMutableDictionary alloc] init]; - [payload setObject:createISO8601FormattedString([NSDate date]) forKey:@"sent_at"]; - [payload setObject:batch forKey:@"batch"]; - [payload setObject:self.configuration.apiKey forKey:@"api_key"]; - - PHGLog(@"%@ Flushing %lu of %lu queued API calls.", self, (unsigned long)batch.count, (unsigned long)self.queue.count); - PHGLog(@"Flushing batch %@.", payload); - - self.batchRequest = [self.httpClient upload:payload host:self.configuration.host completionHandler:^(BOOL retry) { - [self dispatchBackground:^{ - if (retry) { - [self notifyForName:PHGPostHogRequestDidFailNotification userInfo:batch]; - self.batchRequest = nil; - [self endBackgroundTask]; - return; - } - - [self.queue removeObjectsInArray:batch]; - [self persistQueue]; - [self notifyForName:PHGPostHogRequestDidSucceedNotification userInfo:batch]; - self.batchRequest = nil; - [self endBackgroundTask]; - }]; - }]; - - [self notifyForName:PHGPostHogDidSendRequestNotification userInfo:batch]; -} - -- (void)applicationDidEnterBackground -{ - [self beginBackgroundTask]; - // We are gonna try to flush as much as we reasonably can when we enter background - // since there is a chance that the user will never launch the app again. - [self flush]; -} - -- (void)applicationWillTerminate -{ - [self dispatchBackgroundAndWait:^{ - if (self.queue.count) - [self persistQueue]; - }]; -} - -#pragma mark - Private - -- (NSMutableArray *)queue -{ - if (!_queue) { - _queue = [[self.fileStorage arrayForKey:kPHGQueueFilename] ?: @[] mutableCopy]; - } - - return _queue; -} - -- (NSUInteger)maxBatchSize -{ - return 100; -} - -- (NSString *)getDistinctId -{ -#if TARGET_OS_TV - return [[NSUserDefaults standardUserDefaults] valueForKey:PHGDistinctIdKey]; -#else - return [self.fileStorage stringForKey:kPHGDistinctIdFilename]; -#endif -} - -- (void)saveGroup:(NSString *)groupType groupKey:(NSString *)groupKey -{ - NSDictionary *currentGroups = [self getGroups]; - NSMutableDictionary *newGroups = [currentGroups mutableCopy]; - [newGroups setObject:groupKey forKey:groupType]; - -#if TARGET_OS_TV - [self.userDefaultsStorage setDictionary:newGroups forKey:PHGGroups]; -#else - [self.fileStorage setDictionary:newGroups forKey:kPHGGroups]; -#endif -} - -- (NSDictionary *)getGroups -{ -#if TARGET_OS_TV - NSDictionary *groups = [self.userDefaultsStorage dictionaryForKey:PHGGroups]; -#else - NSDictionary *groups = [self.fileStorage dictionaryForKey:kPHGGroups]; -#endif - -// if groups doesn't exist, return a new empty dict - if (!groups){ - return [[NSDictionary alloc] init]; - } - return groups; -} - -- (void)receivedFeatureFlags:(NSDictionary *)flags payloads:(nonnull NSDictionary *)payloads -{ -#if TARGET_OS_TV - [self.userDefaultsStorage setDictionary:flags forKey:PHGEnabledFeatureFlags]; - [self.userDefaultsStorage setDictionary:payloads forKey:PHGEnabledFeatureFlagPayloads]; -#else - [self.fileStorage setDictionary:flags forKey:kPHGEnabledFeatureFlags]; - [self.fileStorage setDictionary:payloads forKey:kPHGEnabledFeatureFlagPayloads]; -#endif -} - -- (NSArray *)getFeatureFlags -{ - NSDictionary *dict = [self getFeatureFlagsAndValues]; - NSArray *keys = [dict allKeys]; - return keys; -} - -- (NSDictionary *)getFeatureFlagsAndValues -{ -#if TARGET_OS_TV - NSDictionary *dict = [self.userDefaultsStorage dictionaryForKey:PHGEnabledFeatureFlags]; -#else - NSDictionary *dict = [self.fileStorage dictionaryForKey:kPHGEnabledFeatureFlags]; -#endif - return dict; -} - -- (NSDictionary *)getFeatureFlagPayloads -{ -#if TARGET_OS_TV - NSDictionary *dict = [self.userDefaultsStorage dictionaryForKey:PHGEnabledFeatureFlagPayloads]; -#else - NSDictionary *dict = [self.fileStorage dictionaryForKey:kPHGEnabledFeatureFlagPayloads]; -#endif - return dict; -} - -- (void)persistQueue -{ - [self.fileStorage setArray:[self.queue copy] forKey:kPHGQueueFilename]; -} - -@end diff --git a/PostHog/Internal/PHGPostHogUtils.h b/PostHog/Internal/PHGPostHogUtils.h deleted file mode 100644 index b87cdf34f..000000000 --- a/PostHog/Internal/PHGPostHogUtils.h +++ /dev/null @@ -1,49 +0,0 @@ -#import -#import "PHGSerializableValue.h" - -NS_ASSUME_NONNULL_BEGIN - -NSString *createUUIDString(void); - -// Validation Utils -BOOL serializableDictionaryTypes(NSDictionary *dict); - -// Date Utils -NSString *createISO8601FormattedString(NSDate *date); - -void trimQueueItems(NSMutableArray *array, NSUInteger size); - -// Async Utils -dispatch_queue_t phg_dispatch_queue_create_specific(const char *label, - dispatch_queue_attr_t _Nullable attr); -BOOL phg_dispatch_is_on_specific_queue(dispatch_queue_t queue); -void phg_dispatch_specific(dispatch_queue_t queue, dispatch_block_t block, - BOOL waitForCompletion); -void phg_dispatch_specific_async(dispatch_queue_t queue, - dispatch_block_t block); -void phg_dispatch_specific_sync(dispatch_queue_t queue, dispatch_block_t block); - -// Logging - -void PHGSetShowDebugLogs(BOOL showDebugLogs); -void PHGLog(NSString *format, ...); - -// JSON Utils - -JSON_DICT PHGCoerceDictionary(NSDictionary *_Nullable dict); - -NSString *PHGEventNameForScreenTitle(NSString *title); - -// Deep copy and check NSCoding conformance -@protocol PHGSerializableDeepCopy --(id _Nullable) serializableDeepCopy; -@end - -@interface NSDictionary(SerializableDeepCopy) -@end - -@interface NSArray(SerializableDeepCopy) -@end - - -NS_ASSUME_NONNULL_END diff --git a/PostHog/Internal/PHGPostHogUtils.m b/PostHog/Internal/PHGPostHogUtils.m deleted file mode 100644 index 7781839b7..000000000 --- a/PostHog/Internal/PHGPostHogUtils.m +++ /dev/null @@ -1,241 +0,0 @@ -#import "PHGPostHogUtils.h" - -static BOOL kPostHogLoggerShowLogs = NO; - -NSString *createUUIDString() -{ - CFUUIDRef theUUID = CFUUIDCreate(NULL); - NSString *UUIDString = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, theUUID); - CFRelease(theUUID); - return UUIDString; -} - -// Date Utils -NSString *createISO8601FormattedString(NSDate *date) -{ - static NSDateFormatter *dateFormatter; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - dateFormatter = [[NSDateFormatter alloc] init]; - dateFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; - dateFormatter.dateFormat = @"yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSS'Z'"; - dateFormatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; - }); - return [dateFormatter stringFromDate:date]; -} - -/** trim the queue so that it contains only upto `max` number of elements. */ -void trimQueueItems(NSMutableArray *queue, NSUInteger max) -{ - if (queue.count < max) { - return; - } - - // Previously we didn't cap the queue. Hence there are cases where - // the queue may already be larger than 1000 events. Delete as many - // events as required to trim the queue size. - NSRange range = NSMakeRange(0, queue.count - max); - [queue removeObjectsInRange:range]; -} - -// Async Utils -dispatch_queue_t -phg_dispatch_queue_create_specific(const char *label, - dispatch_queue_attr_t attr) -{ - dispatch_queue_t queue = dispatch_queue_create(label, attr); - dispatch_queue_set_specific(queue, (__bridge const void *)queue, - (__bridge void *)queue, NULL); - return queue; -} - -BOOL phg_dispatch_is_on_specific_queue(dispatch_queue_t queue) -{ - return dispatch_get_specific((__bridge const void *)queue) != NULL; -} - -void phg_dispatch_specific(dispatch_queue_t queue, dispatch_block_t block, - BOOL waitForCompletion) -{ - dispatch_block_t autoreleasing_block = ^{ - @autoreleasepool - { - block(); - } - }; - if (dispatch_get_specific((__bridge const void *)queue)) { - autoreleasing_block(); - } else if (waitForCompletion) { - dispatch_sync(queue, autoreleasing_block); - } else { - dispatch_async(queue, autoreleasing_block); - } -} - -void phg_dispatch_specific_async(dispatch_queue_t queue, - dispatch_block_t block) -{ - phg_dispatch_specific(queue, block, NO); -} - -void phg_dispatch_specific_sync(dispatch_queue_t queue, - dispatch_block_t block) -{ - phg_dispatch_specific(queue, block, YES); -} - -// Logging - -void PHGSetShowDebugLogs(BOOL showDebugLogs) -{ - kPostHogLoggerShowLogs = showDebugLogs; -} - -void PHGLog(NSString *format, ...) -{ - if (!kPostHogLoggerShowLogs) - return; - - va_list args; - va_start(args, format); - NSLogv(format, args); - va_end(args); -} - -// JSON Utils - -static id PHGCoerceJSONObject(id obj) -{ - // if the object is a NSString, NSNumber - // then we're good - if ([obj isKindOfClass:[NSString class]] || - [obj isKindOfClass:[NSNumber class]] || - [obj isKindOfClass:[NSNull class]]) { - return obj; - } - - if ([obj isKindOfClass:[NSArray class]]) { - NSMutableArray *array = [NSMutableArray array]; - for (id i in obj) { - NSObject *value = i; - // Hotfix: Storage format should support NSNull instead - if ([value isKindOfClass:[NSNull class]]) { - value = [NSData data]; - } - [array addObject:PHGCoerceJSONObject(value)]; - } - return array; - } - - if ([obj isKindOfClass:[NSDictionary class]]) { - NSMutableDictionary *dict = [NSMutableDictionary dictionary]; - for (NSString *key in obj) { - NSObject *value = obj[key]; - if (![key isKindOfClass:[NSString class]]) - PHGLog(@"warning: dictionary keys should be strings. got: %@. coercing " - @"to: %@", - [key class], [key description]); - dict[key.description] = PHGCoerceJSONObject(value); - } - return dict; - } - - if ([obj isKindOfClass:[NSDate class]]) - return createISO8601FormattedString(obj); - - if ([obj isKindOfClass:[NSURL class]]) - return [obj absoluteString]; - - // default to sending the object's description - PHGLog(@"warning: dictionary values should be valid json types. got: %@. " - @"coercing to: %@", - [obj class], [obj description]); - return [obj description]; -} - -NSDictionary *PHGCoerceDictionary(NSDictionary *dict) -{ - // make sure that a new dictionary exists even if the input is null - dict = dict ?: @{}; - // assert that the proper types are in the dictionary - dict = [dict serializableDeepCopy]; - // coerce urls, and dates to the proper format - return PHGCoerceJSONObject(dict); -} - -NSString *PHGEventNameForScreenTitle(NSString *title) -{ - return [[NSString alloc] initWithFormat:@"Viewed %@ Screen", title]; -} - - -@implementation NSDictionary(SerializableDeepCopy) - -- (NSDictionary *)serializableDeepCopy -{ - NSMutableDictionary *returnDict = [[NSMutableDictionary alloc] initWithCapacity:self.count]; - NSArray *keys = [self allKeys]; - for (id key in keys) { - id aValue = [self objectForKey:key]; - id theCopy = nil; - - if (![aValue conformsToProtocol:@protocol(NSCoding)]) { -#ifdef DEBUG - NSAssert(FALSE, @"key `%@` doesn't conform to NSCoding and can't be serialized for delivery.", key); -#else - PHGLog(@"key `%@` doesn't conform to NSCoding and can't be serialized for delivery.", key); - // simply leave it out since we can't encode it anyway. - continue; -#endif - } - - if ([aValue conformsToProtocol:@protocol(PHGSerializableDeepCopy)]) { - theCopy = [aValue serializableDeepCopy]; - } else if ([aValue conformsToProtocol:@protocol(NSCopying)]) { - theCopy = [aValue copy]; - } else { - theCopy = aValue; - } - - [returnDict setValue:theCopy forKey:key]; - } - - return [returnDict copy]; -} - -@end - - -@implementation NSArray(SerializableDeepCopy) - --(NSArray *)serializableDeepCopy -{ - NSMutableArray *returnArray = [[NSMutableArray alloc] initWithCapacity:self.count]; - - for (id aValue in self) { - id theCopy = nil; - - if (![aValue conformsToProtocol:@protocol(NSCoding)]) { -#ifdef DEBUG - NSAssert(FALSE, @"type `%@` doesn't conform to NSCoding and can't be serialized for delivery.", NSStringFromClass([aValue class])); -#else - PHGLog(@"type `%@` doesn't conform to NSCoding and can't be serialized for delivery.", NSStringFromClass([aValue class])); - // simply leave it out since we can't encode it anyway. - continue; -#endif - } - - if ([aValue conformsToProtocol:@protocol(PHGSerializableDeepCopy)]) { - theCopy = [aValue serializableDeepCopy]; - } else if ([aValue conformsToProtocol:@protocol(NSCopying)]) { - theCopy = [aValue copy]; - } else { - theCopy = aValue; - } - [returnArray addObject:theCopy]; - } - - return [returnArray copy]; -} - -@end diff --git a/PostHog/Internal/PHGStorage.h b/PostHog/Internal/PHGStorage.h deleted file mode 100644 index 7874db4e6..000000000 --- a/PostHog/Internal/PHGStorage.h +++ /dev/null @@ -1,25 +0,0 @@ -#import -#import "PHGCrypto.h" - -@protocol PHGStorage - -@property (nonatomic, strong, nullable) id crypto; - -- (void)removeKey:(NSString *_Nonnull)key; -- (void)resetAll; - -- (void)setData:(NSData *_Nullable)data forKey:(NSString *_Nonnull)key; -- (NSData *_Nullable)dataForKey:(NSString *_Nonnull)key; - -- (void)setDictionary:(NSDictionary *_Nullable)dictionary forKey:(NSString *_Nonnull)key; -- (NSDictionary *_Nullable)dictionaryForKey:(NSString *_Nonnull)key; - -- (void)setArray:(NSArray *_Nullable)array forKey:(NSString *_Nonnull)key; -- (NSArray *_Nullable)arrayForKey:(NSString *_Nonnull)key; - -- (void)setString:(NSString *_Nullable)string forKey:(NSString *_Nonnull)key; -- (NSString *_Nullable)stringForKey:(NSString *_Nonnull)key; - -// Number and Booleans are intentionally omitted at the moment because they are not needed - -@end diff --git a/PostHog/Internal/PHGStoreKitCapturer.h b/PostHog/Internal/PHGStoreKitCapturer.h deleted file mode 100644 index f1a2f610c..000000000 --- a/PostHog/Internal/PHGStoreKitCapturer.h +++ /dev/null @@ -1,14 +0,0 @@ -#import -#import -#import "PHGPostHog.h" - -NS_ASSUME_NONNULL_BEGIN - - -@interface PHGStoreKitCapturer : NSObject - -+ (instancetype)captureTransactionsForPostHog:(PHGPostHog *)posthog; - -@end - -NS_ASSUME_NONNULL_END diff --git a/PostHog/Internal/PHGStoreKitCapturer.m b/PostHog/Internal/PHGStoreKitCapturer.m deleted file mode 100644 index dcce98bb1..000000000 --- a/PostHog/Internal/PHGStoreKitCapturer.m +++ /dev/null @@ -1,97 +0,0 @@ -#import "PHGStoreKitCapturer.h" - - -@interface PHGStoreKitCapturer () - -@property (nonatomic, readonly) PHGPostHog *posthog; -@property (nonatomic, readonly) NSMutableDictionary *transactions; -@property (nonatomic, readonly) NSMutableDictionary *productRequests; - -@end - - -@implementation PHGStoreKitCapturer - -+ (instancetype)captureTransactionsForPostHog:(PHGPostHog *)posthog -{ - return [[PHGStoreKitCapturer alloc] initWithPostHog:posthog]; -} - -- (instancetype)initWithPostHog:(PHGPostHog *)posthog -{ - if (self = [self init]) { - _posthog = posthog; - _productRequests = [NSMutableDictionary dictionaryWithCapacity:1]; - _transactions = [NSMutableDictionary dictionaryWithCapacity:1]; - - [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; - } - return self; -} - -- (void)dealloc -{ - [[SKPaymentQueue defaultQueue] removeTransactionObserver:self]; -} - -#pragma mark - SKPaymentQueue Observer -- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions -{ - for (SKPaymentTransaction *transaction in transactions) { - if (transaction.transactionState != SKPaymentTransactionStatePurchased) { - continue; - } - - SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:transaction.payment.productIdentifier]]; - @synchronized(self) - { - [self.transactions setObject:transaction forKey:transaction.payment.productIdentifier]; - [self.productRequests setObject:request forKey:transaction.payment.productIdentifier]; - } - request.delegate = self; - [request start]; - } -} - -#pragma mark - SKProductsRequest delegate -- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response -{ - for (SKProduct *product in response.products) { - @synchronized(self) - { - SKPaymentTransaction *transaction = [self.transactions objectForKey:product.productIdentifier]; - [self captureTransaction:transaction forProduct:product]; - [self.transactions removeObjectForKey:product.productIdentifier]; - [self.productRequests removeObjectForKey:product.productIdentifier]; - } - } -} - -#pragma mark - Capture -- (void)captureTransaction:(SKPaymentTransaction *)transaction forProduct:(SKProduct *)product -{ - // it seems the identifier is nil for renewable subscriptions - // see http://stackoverflow.com/questions/14827059/skpaymenttransactions-originaltransaction-transactionreceipt-nil-for-restore-on - if (transaction.transactionIdentifier == nil) { - return; - } - - NSString *currency = [product.priceLocale objectForKey:NSLocaleCurrencyCode]; - - [self.posthog capture:@"Order Completed" properties:@{ - @"orderId" : transaction.transactionIdentifier, - @"affiliation" : @"App Store", - @"currency" : currency ?: @"", - @"products" : @[ - @{ - @"sku" : transaction.transactionIdentifier, - @"quantity" : @(transaction.payment.quantity), - @"productId" : product.productIdentifier ?: @"", - @"price" : product.price ?: @0, - @"name" : product.localizedTitle ?: @"", - } - ] - }]; -} - -@end diff --git a/PostHog/Internal/PHGUserDefaultsStorage.h b/PostHog/Internal/PHGUserDefaultsStorage.h deleted file mode 100644 index 85fb2d580..000000000 --- a/PostHog/Internal/PHGUserDefaultsStorage.h +++ /dev/null @@ -1,13 +0,0 @@ -#import -#import "PHGStorage.h" - - -@interface PHGUserDefaultsStorage : NSObject - -@property (nonatomic, strong, nullable) id crypto; -@property (nonnull, nonatomic, readonly) NSUserDefaults *defaults; -@property (nullable, nonatomic, readonly) NSString *namespacePrefix; - -- (instancetype _Nonnull)initWithDefaults:(NSUserDefaults *_Nonnull)defaults namespacePrefix:(NSString *_Nullable)namespacePrefix crypto:(id _Nullable)crypto; - -@end diff --git a/PostHog/Internal/PHGUserDefaultsStorage.m b/PostHog/Internal/PHGUserDefaultsStorage.m deleted file mode 100644 index f6afc4eb7..000000000 --- a/PostHog/Internal/PHGUserDefaultsStorage.m +++ /dev/null @@ -1,157 +0,0 @@ -#import "PHGUtils.h" -#import "PHGUserDefaultsStorage.h" -#import "PHGCrypto.h" - - -@implementation PHGUserDefaultsStorage - -- (instancetype)initWithDefaults:(NSUserDefaults *)defaults namespacePrefix:(NSString *)namespacePrefix crypto:(id)crypto -{ - if (self = [super init]) { - _defaults = defaults; - _namespacePrefix = namespacePrefix; - _crypto = crypto; - } - return self; -} - -- (void)removeKey:(NSString *)key -{ - [self.defaults removeObjectForKey:[self namespacedKey:key]]; -} - -- (void)resetAll -{ - // Courtesy of http://stackoverflow.com/questions/6358737/nsuserdefaults-reset - if (!self.namespacePrefix) { - NSString *domainName = [[NSBundle mainBundle] bundleIdentifier]; - if (domainName) { - [self.defaults removePersistentDomainForName:domainName]; - return; - } - } - for (NSString *key in self.defaults.dictionaryRepresentation.allKeys) { - if (!self.namespacePrefix || [key hasPrefix:self.namespacePrefix]) { - [self.defaults removeObjectForKey:key]; - } - } - [self.defaults synchronize]; -} - -- (void)setObject:(id)object forKey:(NSString *)key -{ - // pass through for NSUserDefaults to remove keys if supplied a nil value. - if (object) { - [self.defaults setObject:object forKey:key]; - } else { - [self.defaults removeObjectForKey:key]; - } -} - -- (void)setData:(NSData *)data forKey:(NSString *)key -{ - key = [self namespacedKey:key]; - if (!self.crypto) { - [self.defaults setObject:data forKey:key]; - return; - } - NSData *encryptedData = [self.crypto encrypt:data]; - [self setObject:encryptedData forKey:key]; -} - -- (NSData *)dataForKey:(NSString *)key -{ - key = [self namespacedKey:key]; - if (!self.crypto) { - return [self.defaults objectForKey:key]; - } - NSData *data = [self.defaults objectForKey:key]; - if (!data) { - PHGLog(@"WARNING: No data file for key %@", key); - return nil; - } - return [self.crypto decrypt:data]; -} - -- (NSDictionary *)dictionaryForKey:(NSString *)key -{ - if (!self.crypto) { - key = [self namespacedKey:key]; - return [self.defaults dictionaryForKey:key]; - } - return [self plistForKey:key]; -} - -- (void)setDictionary:(NSDictionary *)dictionary forKey:(NSString *)key -{ - if (!self.crypto) { - key = [self namespacedKey:key]; - [self setObject:dictionary forKey:key]; - return; - } - [self setPlist:dictionary forKey:key]; -} - -- (NSArray *)arrayForKey:(NSString *)key -{ - if (!self.crypto) { - key = [self namespacedKey:key]; - return [self.defaults arrayForKey:key]; - } - return [self plistForKey:key]; -} - -- (void)setArray:(NSArray *)array forKey:(NSString *)key -{ - if (!self.crypto) { - key = [self namespacedKey:key]; - [self setObject:array forKey:key]; - return; - } - [self setPlist:array forKey:key]; -} - -- (NSString *)stringForKey:(NSString *)key -{ - if (!self.crypto) { - key = [self namespacedKey:key]; - return [self.defaults stringForKey:key]; - } - return [self plistForKey:key]; -} - -- (void)setString:(NSString *)string forKey:(NSString *)key -{ - if (!self.crypto) { - key = [self namespacedKey:key]; - [self setObject:string forKey:key]; - return; - } - [self setPlist:string forKey:key]; -} - -#pragma mark - Helpers - -- (id _Nullable)plistForKey:(NSString *)key -{ - NSData *data = [self dataForKey:key]; - return data ? [PHGUtils plistFromData:data] : nil; -} - -- (void)setPlist:(id _Nonnull)plist forKey:(NSString *)key -{ - NSData *data = [PHGUtils dataFromPlist:plist]; - if (data) { - [self setData:data forKey:key]; - } -} - -- (NSString *)namespacedKey:(NSString *)key -{ - if (self.namespacePrefix) { - return [NSString stringWithFormat:@"%@.%@", self.namespacePrefix, key]; - } - return key; -} - -@end diff --git a/PostHog/Internal/PHGUtils.h b/PostHog/Internal/PHGUtils.h deleted file mode 100644 index bc51b4467..000000000 --- a/PostHog/Internal/PHGUtils.h +++ /dev/null @@ -1,12 +0,0 @@ -#import -#import "PHGPostHogUtils.h" - - -@interface PHGUtils : NSObject - -+ (NSData *_Nullable)dataFromPlist:(nonnull id)plist; -+ (id _Nullable)plistFromData:(NSData *_Nonnull)data; - -+ (id _Nullable)traverseJSON:(id _Nullable)object andReplaceWithFilters:(nonnull NSDictionary*)patterns; - -@end \ No newline at end of file diff --git a/PostHog/Internal/PHGUtils.m b/PostHog/Internal/PHGUtils.m deleted file mode 100644 index c13908adf..000000000 --- a/PostHog/Internal/PHGUtils.m +++ /dev/null @@ -1,85 +0,0 @@ -#import "PHGUtils.h" - -@implementation PHGUtils - -+ (NSData *_Nullable)dataFromPlist:(nonnull id)plist -{ - NSError *error = nil; - NSData *data = [NSPropertyListSerialization dataWithPropertyList:plist - format:NSPropertyListXMLFormat_v1_0 - options:0 - error:&error]; - if (error) { - PHGLog(@"Unable to serialize data from plist object", error, plist); - } - return data; -} - -+ (id _Nullable)plistFromData:(NSData *_Nonnull)data -{ - NSError *error = nil; - id plist = [NSPropertyListSerialization propertyListWithData:data - options:0 - format:nil - error:&error]; - if (error) { - PHGLog(@"Unable to parse plist from data %@", error); - } - return plist; -} - - -+(id)traverseJSON:(id)object andReplaceWithFilters:(NSDictionary*)patterns -{ - if ([object isKindOfClass:NSDictionary.class]) { - NSDictionary* dict = object; - NSMutableDictionary* newDict = [NSMutableDictionary dictionaryWithCapacity:dict.count]; - - for (NSString* key in dict.allKeys) { - newDict[key] = [self traverseJSON:dict[key] andReplaceWithFilters:patterns]; - } - - return newDict; - } - - if ([object isKindOfClass:NSArray.class]) { - NSArray* array = object; - NSMutableArray* newArray = [NSMutableArray arrayWithCapacity:array.count]; - - for (int i = 0; i < array.count; i++) { - newArray[i] = [self traverseJSON:array[i] andReplaceWithFilters:patterns]; - } - - return newArray; - } - - if ([object isKindOfClass:NSString.class]) { - NSError* error = nil; - NSMutableString* str = [object mutableCopy]; - - for (NSString* pattern in patterns) { - NSRegularExpression* re = [NSRegularExpression regularExpressionWithPattern:pattern - options:0 - error:&error]; - - if (error) { - @throw error; - } - - NSInteger matches = [re replaceMatchesInString:str - options:0 - range:NSMakeRange(0, str.length) - withTemplate:patterns[pattern]]; - - if (matches > 0) { - PHGLog(@"%@ Redacted value from action: %@", self, pattern); - } - } - - return str; - } - - return object; -} - -@end diff --git a/PostHog/Internal/UIViewController+PHGScreen.h b/PostHog/Internal/UIViewController+PHGScreen.h deleted file mode 100644 index 519b65123..000000000 --- a/PostHog/Internal/UIViewController+PHGScreen.h +++ /dev/null @@ -1,14 +0,0 @@ -#include - -#if !TARGET_OS_OSX -#import -#endif - -@interface UIViewController (PHGScreen) - -+ (BOOL)isAppExtension; - -+ (void)phg_swizzleViewDidAppear; -+ (UIViewController *)phg_topViewController; - -@end diff --git a/PostHog/Internal/UIViewController+PHGScreen.m b/PostHog/Internal/UIViewController+PHGScreen.m deleted file mode 100644 index 73a43f4f5..000000000 --- a/PostHog/Internal/UIViewController+PHGScreen.m +++ /dev/null @@ -1,102 +0,0 @@ -#import "UIViewController+PHGScreen.h" -#import -#import "PHGPostHog.h" -#import "PHGUtils.h" - - -@implementation UIViewController (PHGScreen) - -+ (BOOL)isAppExtension { -#if TARGET_OS_IOS || TARGET_OS_TV - // Documented by Apple - BOOL appExtension = [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]; - return appExtension; -#elif TARGET_OS_OSX - return NO; -#endif -} - -+ (void)phg_swizzleViewDidAppear -{ - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - Class class = [self class]; - - SEL originalSelector = @selector(viewDidAppear:); - SEL swizzledSelector = @selector(phg_viewDidAppear:); - - Method originalMethod = class_getInstanceMethod(class, originalSelector); - Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); - - BOOL didAddMethod = - class_addMethod(class, - originalSelector, - method_getImplementation(swizzledMethod), - method_getTypeEncoding(swizzledMethod)); - - if (didAddMethod) { - class_replaceMethod(class, - swizzledSelector, - method_getImplementation(originalMethod), - method_getTypeEncoding(originalMethod)); - } else { - method_exchangeImplementations(originalMethod, swizzledMethod); - } - }); -} - - -+ (UIViewController *)phg_topViewController -{ - // iOS App extensions should not call [UIApplication sharedApplication], even if UIApplication responds to it. - static Class applicationClass = nil; - if (![UIViewController isAppExtension]) { - Class cls = NSClassFromString(@"UIApplication"); - if (cls && [cls respondsToSelector:NSSelectorFromString(@"sharedApplication")]) { - applicationClass = cls; - } - } - - UIWindow *mainWindow = [[[applicationClass sharedApplication] windows] firstObject]; - UIViewController *root = mainWindow.rootViewController; - return [self phg_topViewController:root]; -} - -+ (UIViewController *)phg_topViewController:(UIViewController *)rootViewController -{ - UIViewController *presentedViewController = rootViewController.presentedViewController; - if (presentedViewController != nil) { - return [self phg_topViewController:presentedViewController]; - } - - if ([rootViewController isKindOfClass:[UINavigationController class]]) { - UIViewController *lastViewController = [[(UINavigationController *)rootViewController viewControllers] lastObject]; - return [self phg_topViewController:lastViewController]; - } - - return rootViewController; -} - -- (void)phg_viewDidAppear:(BOOL)animated -{ - UIViewController *top = [[self class] phg_topViewController]; - if (!top) { - PHGLog(@"Could not infer screen."); - return; - } - - NSString *name = [top title]; - if (!name || name.length == 0) { - name = [[[top class] description] stringByReplacingOccurrencesOfString:@"ViewController" withString:@""]; - // Class name could be just "ViewController". - if (name.length == 0) { - PHGLog(@"Could not infer screen name."); - name = @"Unknown"; - } - } - [[PHGPostHog sharedPostHog] screen:name properties:nil]; - - [self phg_viewDidAppear:animated]; -} - -@end diff --git a/PostHog/Models/PostHogEvent.swift b/PostHog/Models/PostHogEvent.swift new file mode 100644 index 000000000..57829aff2 --- /dev/null +++ b/PostHog/Models/PostHogEvent.swift @@ -0,0 +1,81 @@ +// +// PostHogEvent.swift +// PostHog +// +// Created by Manoel Aranda Neto on 13.10.23. +// + +import Foundation + +public class PostHogEvent { + public var event: String + public var distinctId: String + public var properties: [String: Any] + public var timestamp: Date + public var uuid: UUID + + enum Key: String { + case event + case distinctId + case properties + case timestamp + case uuid + } + + init(event: String, distinctId: String, properties: [String: Any]? = nil, timestamp: Date = Date(), uuid: UUID = .init()) { + self.event = event + self.distinctId = distinctId + self.properties = properties ?? [:] + self.timestamp = timestamp + self.uuid = uuid + } + + // NOTE: Ideally we would use the NSCoding behaviour but it gets needlessly complex + // given we only need this for sending to the API + static func fromJSON(_ data: Data) -> PostHogEvent? { + guard let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { + return nil + } + + return fromJSON(json) + } + + static func fromJSON(_ json: [String: Any]) -> PostHogEvent? { + guard let event = json["event"] as? String else { return nil } + + let timestamp = json["timestamp"] as? String ?? toISO8601String(Date()) + + let timestampDate = toISO8601Date(timestamp) ?? Date() + + var properties = (json["properties"] as? [String: Any]) ?? [:] + + // back compatibility with v2 + let setProps = json["$set"] as? [String: Any] + if setProps != nil { + properties["$set"] = setProps + } + + guard let distinctId = (json["distinct_id"] as? String) ?? (properties["distinct_id"] as? String) else { return nil } + + let uuid = ((json["uuid"] as? String) ?? (json["message_id"] as? String)) ?? UUID().uuidString + let uuidObj = UUID(uuidString: uuid) ?? UUID() + + return PostHogEvent( + event: event, + distinctId: distinctId, + properties: properties, + timestamp: timestampDate, + uuid: uuidObj + ) + } + + func toJSON() -> [String: Any] { + [ + "event": event, + "distinct_id": distinctId, + "properties": properties, + "timestamp": toISO8601String(timestamp), + "uuid": uuid.uuidString, + ] + } +} diff --git a/PostHog/PostHog.h b/PostHog/PostHog.h index 40f03dc8c..c7a8d3e6f 100644 --- a/PostHog/PostHog.h +++ b/PostHog/PostHog.h @@ -1,4 +1,12 @@ -#import +// +// PostHog.h +// PostHog +// +// Created by Ben White on 10.01.23. +// + + +#import //! Project version number for PostHog. FOUNDATION_EXPORT double PostHogVersionNumber; @@ -6,7 +14,4 @@ FOUNDATION_EXPORT double PostHogVersionNumber; //! Project version string for PostHog. FOUNDATION_EXPORT const unsigned char PostHogVersionString[]; -#import "PHGPostHog.h" -#import "PHGPostHogIntegration.h" -#import "PHGContext.h" -#import "PHGMiddleware.h" +// In this header, you should import all the public headers of your framework using statements like #import diff --git a/PostHog/PostHogApi.swift b/PostHog/PostHogApi.swift new file mode 100644 index 000000000..9a96f0c0a --- /dev/null +++ b/PostHog/PostHogApi.swift @@ -0,0 +1,136 @@ +// +// PostHogApi.swift +// PostHog +// +// Created by Ben White on 06.02.23. +// + +import Foundation + +class PostHogApi { + private let config: PostHogConfig + + init(_ config: PostHogConfig) { + self.config = config + } + + func sessionConfig() -> URLSessionConfiguration { + let config = URLSessionConfiguration.default + + config.httpAdditionalHeaders = [ + "Content-Type": "application/json; charset=utf-8", + "User-Agent": "posthog-ios/\(postHogVersion)", + ] + + return config + } + + func batch(events: [PostHogEvent], completion: @escaping (PostHogBatchUploadInfo) -> Void) { + guard let url = URL(string: "batch", relativeTo: config.host) else { + return completion(PostHogBatchUploadInfo(statusCode: nil, error: nil)) + } + + let config = sessionConfig() + var headers = config.httpAdditionalHeaders ?? [:] + headers["Accept-Encoding"] = "gzip" + headers["Content-Encoding"] = "gzip" + config.httpAdditionalHeaders = headers + + var request = URLRequest(url: url) + request.httpMethod = "POST" + + let toSend: [String: Any] = [ + "api_key": self.config.apiKey, + "batch": events.map { $0.toJSON() }, + "sent_at": toISO8601String(Date()), + ] + + var data: Data? + + do { + data = try JSONSerialization.data(withJSONObject: toSend) + } catch { + return completion(PostHogBatchUploadInfo(statusCode: nil, error: error)) + } + + var gzippedPayload: Data? + do { + gzippedPayload = try data!.gzipped() + } catch { + return completion(PostHogBatchUploadInfo(statusCode: nil, error: error)) + } + + URLSession(configuration: config).uploadTask(with: request, from: gzippedPayload!) { data, response, error in + if error != nil { + return completion(PostHogBatchUploadInfo(statusCode: nil, error: error)) + } + + let httpResponse = response as! HTTPURLResponse + + if !(200 ... 299 ~= httpResponse.statusCode) { + do { + try hedgeLog("Error sending events to PostHog: \(JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any])") + } catch { + hedgeLog("Error sending events to PostHog") + } + } + + return completion(PostHogBatchUploadInfo(statusCode: httpResponse.statusCode, error: error)) + }.resume() + } + + func decide( + distinctId: String, + anonymousId: String, + groups: [String: String], + completion: @escaping ([String: Any]?, _ error: Error?) -> Void + ) { + var urlComps = URLComponents() + urlComps.path = "/decide" + urlComps.queryItems = [URLQueryItem(name: "v", value: "3")] + + guard let url = urlComps.url(relativeTo: config.host) else { + return completion(nil, nil) + } + + let config = sessionConfig() + + var request = URLRequest(url: url) + request.httpMethod = "POST" + + let toSend: [String: Any] = [ + "api_key": self.config.apiKey, + "distinct_id": distinctId, + "$anon_distinct_id": anonymousId, + "$groups": groups, + ] + + var data: Data? + + do { + data = try JSONSerialization.data(withJSONObject: toSend) + } catch { + return completion(nil, error) + } + + URLSession(configuration: config).uploadTask(with: request, from: data!) { data, response, error in + if error != nil { + return completion(nil, error) + } + + let httpResponse = response as! HTTPURLResponse + + if !(200 ... 299 ~= httpResponse.statusCode) { + return completion(nil, + InternalPostHogError(description: "/decide returned a non 2xx status: \(httpResponse.statusCode)")) + } + + do { + let jsonData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String: Any] + completion(jsonData, nil) + } catch { + completion(nil, error) + } + }.resume() + } +} diff --git a/PostHog/PostHogBatchUploadInfo.swift b/PostHog/PostHogBatchUploadInfo.swift new file mode 100644 index 000000000..9b4d607e0 --- /dev/null +++ b/PostHog/PostHogBatchUploadInfo.swift @@ -0,0 +1,13 @@ +// +// PostHogBatchUploadInfo.swift +// PostHog +// +// Created by Manoel Aranda Neto on 13.10.23. +// + +import Foundation + +struct PostHogBatchUploadInfo { + let statusCode: Int? + let error: Error? +} diff --git a/PostHog/PostHogConfig.swift b/PostHog/PostHogConfig.swift new file mode 100644 index 000000000..eb171de22 --- /dev/null +++ b/PostHog/PostHogConfig.swift @@ -0,0 +1,51 @@ +// +// PostHogConfig.swift +// PostHog +// +// Created by Ben White on 07.02.23. +// +import Foundation + +@objc(PostHogConfig) public class PostHogConfig: NSObject { + @objc(PostHogDataMode) public enum PostHogDataMode: Int { + case wifi + case cellular + case any + } + + @objc public let host: URL + @objc public let apiKey: String + @objc public var flushAt: Int = 20 + @objc public var maxQueueSize: Int = 1000 + @objc public var maxBatchSize: Int = 50 + @objc public var flushIntervalSeconds: TimeInterval = 30 + @objc public var dataMode: PostHogDataMode = .any + @objc public var sendFeatureFlagEvent: Bool = true + @objc public var preloadFeatureFlags: Bool = true + @objc public var captureApplicationLifecycleEvents: Bool = true + @objc public var captureScreenViews: Bool = true + @objc public var debug: Bool = false + @objc public var optOut: Bool = false + public static let defaultHost: String = "https://app.posthog.com" + + // only internal + var disableReachabilityForTesting: Bool = false + var disableQueueTimerForTesting: Bool = false + + @objc(apiKey:) + public init( + apiKey: String + ) { + self.apiKey = apiKey + host = URL(string: PostHogConfig.defaultHost)! + } + + @objc(apiKey:host:) + public init( + apiKey: String, + host: String = defaultHost + ) { + self.apiKey = apiKey + self.host = URL(string: host) ?? URL(string: PostHogConfig.defaultHost)! + } +} diff --git a/PostHog/PostHogConsumerPayload.swift b/PostHog/PostHogConsumerPayload.swift new file mode 100644 index 000000000..923752fdf --- /dev/null +++ b/PostHog/PostHogConsumerPayload.swift @@ -0,0 +1,13 @@ +// +// PostHogConsumerPayload.swift +// PostHog +// +// Created by Manoel Aranda Neto on 13.10.23. +// + +import Foundation + +struct PostHogConsumerPayload { + let events: [PostHogEvent] + let completion: (Bool) -> Void +} diff --git a/PostHog/PostHogContext.swift b/PostHog/PostHogContext.swift new file mode 100644 index 000000000..0ea10b306 --- /dev/null +++ b/PostHog/PostHogContext.swift @@ -0,0 +1,116 @@ +// +// PostHogContext.swift +// PostHog +// +// Created by Manoel Aranda Neto on 16.10.23. +// + +import Foundation + +#if os(iOS) || os(tvOS) + import UIKit +#endif + +class PostHogContext { + #if !os(watchOS) + private let reachability: Reachability? + #endif + + private lazy var theStaticContext: [String: Any] = { + // Properties that do not change over the lifecycle of an application + var properties: [String: Any] = [:] + + let infoDictionary = Bundle.main.infoDictionary + + if let appName = infoDictionary?[kCFBundleNameKey as String] { + properties["$app_name"] = appName + } + if let appVersion = infoDictionary?["CFBundleShortVersionString"] { + properties["$app_version"] = appVersion + } + if let appBuild = infoDictionary?["CFBundleVersion"] { + properties["$app_build"] = appBuild + } + + if Bundle.main.bundleIdentifier != nil { + properties["$app_namespace"] = Bundle.main.bundleIdentifier + } + + #if os(iOS) || os(tvOS) + let device = UIDevice.current + // use https://github.com/devicekit/DeviceKit + properties["$device_model"] = platform() + properties["$device_name"] = device.model + properties["$device_manufacturer"] = "Apple" + properties["$os_name"] = device.systemName + properties["$os_version"] = device.systemVersion + + var deviceType: String? + switch device.userInterfaceIdiom { + case UIUserInterfaceIdiom.phone: + deviceType = "Mobile" + case UIUserInterfaceIdiom.pad: + deviceType = "Tablet" + case UIUserInterfaceIdiom.tv: + deviceType = "TV" + case UIUserInterfaceIdiom.carPlay: + deviceType = "CarPlay" + case UIUserInterfaceIdiom.mac: + deviceType = "Desktop" + default: + deviceType = nil + } + if deviceType != nil { + properties["$device_type"] = deviceType + } + #endif + + return properties + }() + + #if !os(watchOS) + init(_ reachability: Reachability?) { + self.reachability = reachability + } + #else + init() {} + #endif + + func staticContext() -> [String: Any] { + theStaticContext + } + + private func platform() -> String { + var size = 0 + sysctlbyname("hw.machine", nil, &size, nil, 0) + var machine = [CChar](repeating: 0, count: size) + sysctlbyname("hw.machine", &machine, &size, nil, 0) + return String(cString: machine) + } + + func dynamicContext() -> [String: Any] { + var properties: [String: Any] = [:] + + #if os(iOS) || os(tvOS) + properties["$screen_width"] = Float(UIScreen.main.bounds.width) + properties["$screen_height"] = Float(UIScreen.main.bounds.height) + #endif + + properties["$lib"] = "posthog-ios" + properties["$lib_version"] = postHogVersion + + if Locale.current.languageCode != nil { + properties["$locale"] = Locale.current.languageCode + } + properties["$timezone"] = TimeZone.current.identifier + + #if !os(watchOS) + if reachability != nil { + properties["$network_wifi"] = reachability?.connection == .wifi + properties["$network_cellular"] = reachability?.connection == .cellular + } + #endif + + return properties + } +} diff --git a/PostHog/PostHogExtensions.swift b/PostHog/PostHogExtensions.swift new file mode 100644 index 000000000..4f701e69b --- /dev/null +++ b/PostHog/PostHogExtensions.swift @@ -0,0 +1,20 @@ +// +// PostHogExtensions.swift +// PostHog +// +// Created by Manoel Aranda Neto on 13.10.23. +// + +import Foundation + +/** + # Notifications + + This helper module encapsulates all notifications that we trigger from within the SDK. + + */ + +public extension PostHogSDK { + @objc static let didStartNotification = Notification.Name("PostHogDidStart") // object: nil + @objc static let didReceiveFeatureFlags = Notification.Name("PostHogDidReceiveFeatureFlags") // object: nil +} diff --git a/PostHog/PostHogFeatureFlags.swift b/PostHog/PostHogFeatureFlags.swift new file mode 100644 index 000000000..2b7d27f8c --- /dev/null +++ b/PostHog/PostHogFeatureFlags.swift @@ -0,0 +1,159 @@ +// +// PostHogFeatureFlags.swift +// PostHog +// +// Created by Manoel Aranda Neto on 10.10.23. +// + +import Foundation + +class PostHogFeatureFlags { + private let config: PostHogConfig + private let storage: PostHogStorage + private let api: PostHogApi + + private let isLoadingLock = NSLock() + private let featureFlagsLock = NSLock() + private var isLoadingFeatureFlags = false + + private let dispatchQueue = DispatchQueue(label: "com.posthog.FeatureFlags", + target: .global(qos: .utility)) + + init(_ config: PostHogConfig, + _ storage: PostHogStorage, + _ api: PostHogApi) + { + self.config = config + self.storage = storage + self.api = api + } + + private func setLoading(_ value: Bool) { + isLoadingLock.withLock { + self.isLoadingFeatureFlags = value + } + } + + func loadFeatureFlags( + distinctId: String, + anonymousId: String, + groups: [String: String], + callback: @escaping () -> Void + ) { + isLoadingLock.withLock { + if self.isLoadingFeatureFlags { + return + } + self.isLoadingFeatureFlags = true + } + + api.decide(distinctId: distinctId, + anonymousId: anonymousId, + groups: groups) + { data, _ in + self.dispatchQueue.async { + guard let featureFlags = data?["featureFlags"] as? [String: Any], + let featureFlagPayloads = data?["featureFlagPayloads"] as? [String: Any] + else { + hedgeLog("Error: Decide response missing correct featureFlags format") + + self.notifyAndRelease() + + return callback() + } + let errorsWhileComputingFlags = data?["errorsWhileComputingFlags"] as? Bool ?? false + + self.featureFlagsLock.withLock { + if errorsWhileComputingFlags { + let cachedFeatureFlags = self.storage.getDictionary(forKey: .enabledFeatureFlags) as? [String: Any] ?? [:] + let cachedFeatureFlagsPayloads = self.storage.getDictionary(forKey: .enabledFeatureFlagPayloads) as? [String: Any] ?? [:] + + let newFeatureFlags = cachedFeatureFlags.merging(featureFlags) { _, new in new } + let newFeatureFlagsPayloads = cachedFeatureFlagsPayloads.merging(featureFlagPayloads) { _, new in new } + + // if not all flags were computed, we upsert flags instead of replacing them + self.storage.setDictionary(forKey: .enabledFeatureFlags, contents: newFeatureFlags) + self.storage.setDictionary(forKey: .enabledFeatureFlagPayloads, contents: newFeatureFlagsPayloads) + } else { + self.storage.setDictionary(forKey: .enabledFeatureFlags, contents: featureFlags) + self.storage.setDictionary(forKey: .enabledFeatureFlagPayloads, contents: featureFlagPayloads) + } + } + + self.notifyAndRelease() + + return callback() + } + } + } + + private func notifyAndRelease() { + DispatchQueue.main.async { + NotificationCenter.default.post(name: PostHogSDK.didReceiveFeatureFlags, object: nil) + } + + setLoading(false) + } + + func getFeatureFlags() -> [String: Any]? { + var flags: [String: Any]? + featureFlagsLock.withLock { + flags = self.storage.getDictionary(forKey: .enabledFeatureFlags) as? [String: Any] + } + + return flags + } + + func isFeatureEnabled(_ key: String) -> Bool { + var flags: [String: Any]? + featureFlagsLock.withLock { + flags = self.storage.getDictionary(forKey: .enabledFeatureFlags) as? [String: Any] + } + + let value = flags?[key] + + if value != nil { + let boolValue = value as? Bool + if boolValue != nil { + return boolValue! + } else { + return true + } + } else { + return false + } + } + + func getFeatureFlag(_ key: String) -> Any? { + var flags: [String: Any]? + featureFlagsLock.withLock { + flags = self.storage.getDictionary(forKey: .enabledFeatureFlags) as? [String: Any] + } + + return flags?[key] + } + + func getFeatureFlagPayload(_ key: String) -> Any? { + var flags: [String: Any]? + featureFlagsLock.withLock { + flags = self.storage.getDictionary(forKey: .enabledFeatureFlagPayloads) as? [String: Any] + } + + let value = flags?[key] + + guard let stringValue = value as? String else { + return value + } + + do { + // The payload value is stored as a string and is not pre-parsed... + // We need to mimic the JSON.parse of JS which is what posthog-js uses + return try JSONSerialization.jsonObject(with: stringValue.data(using: .utf8)!, options: .fragmentsAllowed) + } catch { + hedgeLog("Error parsing the object \(String(describing: value)): \(error)") + } + + // fallbak to original value if not possible to serialize + return value + } +} diff --git a/PostHog/PostHogFileBackedQueue.swift b/PostHog/PostHogFileBackedQueue.swift new file mode 100644 index 000000000..bb47a7873 --- /dev/null +++ b/PostHog/PostHogFileBackedQueue.swift @@ -0,0 +1,109 @@ +// +// PostHogFileBackedQueue.swift +// PostHog +// +// Created by Manoel Aranda Neto on 13.10.23. +// + +import Foundation + +class PostHogFileBackedQueue { + let queue: URL + @ReadWriteLock + private var items = [String]() + + var depth: Int { + items.count + } + + init(queue: URL, oldQueue: URL) { + self.queue = queue + setup(oldQueue: oldQueue) + } + + private func setup(oldQueue: URL?) { + do { + try FileManager.default.createDirectory(atPath: queue.path, withIntermediateDirectories: true) + } catch { + hedgeLog("Error trying to create caching folder \(error)") + } + + if oldQueue != nil { + migrateOldQueue(queue: queue, oldQueue: oldQueue!) + } + + do { + items = try FileManager.default.contentsOfDirectory(atPath: queue.path) + items.sort { Double($0)! < Double($1)! } + } catch { + hedgeLog("Failed to load files for queue \(error)") + // failed to read directory – bad permissions, perhaps? + } + } + + func peek(_ count: Int) -> [Data] { + loadFiles(count) + } + + func delete(index: Int) { + if items.isEmpty { return } + let removed = items.remove(at: index) + + deleteSafely(queue.appendingPathComponent(removed)) + } + + func pop(_ count: Int) { + deleteFiles(count) + } + + func add(_ contents: Data) { + do { + let filename = "\(Date().timeIntervalSince1970)" + try contents.write(to: queue.appendingPathComponent(filename)) + items.append(filename) + } catch { + hedgeLog("Could not write file \(error)") + } + } + + func clear() { + deleteSafely(queue) + setup(oldQueue: nil) + } + + private func loadFiles(_ count: Int) -> [Data] { + var results = [Data]() + + for item in items { + let itemURL = queue.appendingPathComponent(item) + do { + if !FileManager.default.fileExists(atPath: itemURL.path) { + hedgeLog("File \(itemURL) does not exist") + continue + } + let contents = try Data(contentsOf: itemURL) + + results.append(contents) + } catch { + hedgeLog("File \(itemURL) is corrupted \(error)") + + deleteSafely(itemURL) + } + + if results.count == count { + return results + } + } + + return results + } + + private func deleteFiles(_ count: Int) { + for _ in 0 ..< count { + if items.isEmpty { return } + let removed = items.remove(at: 0) // We always remove from the top of the queue + + deleteSafely(queue.appendingPathComponent(removed)) + } + } +} diff --git a/PostHog/PostHogLegacyQueue.swift b/PostHog/PostHogLegacyQueue.swift new file mode 100644 index 000000000..f0938c7b7 --- /dev/null +++ b/PostHog/PostHogLegacyQueue.swift @@ -0,0 +1,45 @@ +// +// PostHogLegacyQueue.swift +// PostHog +// +// Created by Manoel Aranda Neto on 30.10.23. +// + +import Foundation + +// Migrates the Old Queue (v2) to the new Queue (v3) +func migrateOldQueue(queue: URL, oldQueue: URL) { + if !FileManager.default.fileExists(atPath: oldQueue.path) { + return + } + + defer { + deleteSafely(oldQueue) + } + + do { + let data = try Data(contentsOf: oldQueue) + let array = try JSONSerialization.jsonObject(with: data) as? [Any] + + if array == nil { + return + } + + for item in array! { + guard let event = item as? [String: Any] else { + continue + } + let timestamp = event["timestamp"] as? String ?? toISO8601String(Date()) + + let timestampDate = toISO8601Date(timestamp) ?? Date() + + let filename = "\(timestampDate.timeIntervalSince1970)" + + let contents = try JSONSerialization.data(withJSONObject: event) + + try contents.write(to: queue.appendingPathComponent(filename)) + } + } catch { + hedgeLog("Failed to migrate queue \(error)") + } +} diff --git a/PostHog/PostHogQueue.swift b/PostHog/PostHogQueue.swift new file mode 100644 index 000000000..d9975993b --- /dev/null +++ b/PostHog/PostHogQueue.swift @@ -0,0 +1,234 @@ +// +// PostHogQueue.swift +// PostHog +// +// Created by Ben White on 06.02.23. +// + +import Foundation + +/** + # Queue + + The queue uses File persistence. This allows us to + 1. Only send events when we have a network connection + 2. Ensure that we can survive app closing or offline situations + 3. Not hold too much in mempory + + */ + +class PostHogQueue { + private let config: PostHogConfig + private let storage: PostHogStorage + private let api: PostHogApi + private var paused: Bool = false + private var pausedLock = NSLock() + private var pausedUntil: Date? + private var retryCount: TimeInterval = 0 + #if !os(watchOS) + private let reachability: Reachability? + #endif + + private var isFlushing = false + private let isFlushingLock = NSLock() + private var timer: Timer? + private let timerLock = NSLock() + + private let dispatchQueue = DispatchQueue(label: "com.posthog.Queue", target: .global(qos: .utility)) + + var depth: Int { + fileQueue.depth + } + + private let fileQueue: PostHogFileBackedQueue + + #if !os(watchOS) + init(_ config: PostHogConfig, _ storage: PostHogStorage, _ api: PostHogApi, _ reachability: Reachability?) { + self.config = config + self.storage = storage + self.api = api + self.reachability = reachability + fileQueue = PostHogFileBackedQueue(queue: storage.url(forKey: .queue), oldQueue: storage.url(forKey: .oldQeueue)) + } + #else + init(_ config: PostHogConfig, _ storage: PostHogStorage, _ api: PostHogApi) { + self.config = config + self.storage = storage + self.api = api + fileQueue = PostHogFileBackedQueue(queue: storage.url(forKey: .queue), oldQueue: storage.url(forKey: .oldQeueue)) + } + #endif + + private func eventHandler(_ payload: PostHogConsumerPayload) { + hedgeLog("Sending batch of \(payload.events.count) events to PostHog") + + api.batch(events: payload.events) { result in + // -1 means its not anything related to the API but rather network or something else, so we try again + let statusCode = result.statusCode ?? -1 + + var shouldRetry = false + if 300 ... 399 ~= statusCode || statusCode == -1 { + shouldRetry = true + } + + if shouldRetry { + self.retryCount += 1 + let delay = min(self.retryCount * retryDelay, maxRetryDelay) + self.pauseFor(seconds: delay) + hedgeLog("Pausing queue consumption for \(delay) seconds due to \(self.retryCount) API failure(s).") + } else { + self.retryCount = 0 + } + + payload.completion(!shouldRetry) + } + } + + func start(disableReachabilityForTesting: Bool, + disableQueueTimerForTesting: Bool) + { + if !disableReachabilityForTesting { + // Setup the monitoring of network status for the queue + #if !os(watchOS) + reachability?.whenReachable = { reachability in + self.pausedLock.withLock { + if self.config.dataMode == .wifi, reachability.connection != .wifi { + hedgeLog("Queue is paused because its not in WiFi mode") + self.paused = true + } else { + self.paused = false + } + } + + // Always trigger a flush when we are on wifi + if reachability.connection == .wifi { + if !self.isFlushing { + self.flush() + } + } + } + + reachability?.whenUnreachable = { _ in + self.pausedLock.withLock { + hedgeLog("Queue is paused because network is unreachable") + self.paused = true + } + } + + do { + try reachability?.startNotifier() + } catch { + hedgeLog("Error: Unable to monitor network reachability") + } + #endif + } + + if !disableQueueTimerForTesting { + timerLock.withLock { + timer = Timer.scheduledTimer(withTimeInterval: config.flushIntervalSeconds, repeats: true, block: { _ in + if !self.isFlushing { + self.flush() + } + }) + } + } + } + + func clear() { + fileQueue.clear() + } + + func stop() { + timerLock.withLock { + timer?.invalidate() + timer = nil + } + } + + func flush() { + if !canFlush() { + hedgeLog("Already flushing") + return + } + + take(config.maxBatchSize) { payload in + self.eventHandler(payload) + } + } + + private func flushIfOverThreshold() { + if fileQueue.depth >= config.flushAt { + flush() + } + } + + func add(_ event: PostHogEvent) { + var data: Data? + do { + data = try JSONSerialization.data(withJSONObject: event.toJSON()) + } catch { + hedgeLog("Tried to queue unserialisable PostHogEvent \(error)") + return + } + + fileQueue.add(data!) + hedgeLog("Queued event '\(event.event)'. Depth: \(fileQueue.depth)") + flushIfOverThreshold() + } + + private func take(_ count: Int, completion: @escaping (PostHogConsumerPayload) -> Void) { + dispatchQueue.async { + self.isFlushingLock.withLock { + if self.isFlushing { + return + } + self.isFlushing = true + } + + let items = self.fileQueue.peek(count) + + var processing = [PostHogEvent]() + + for item in items { + // each element is a PostHogEvent if fromJSON succeeds + guard let event = PostHogEvent.fromJSON(item) else { + continue + } + processing.append(event) + } + + completion(PostHogConsumerPayload(events: processing) { success in + hedgeLog("Completed!") + if success { + self.fileQueue.pop(items.count) + } + + self.isFlushingLock.withLock { + self.isFlushing = false + } + }) + } + } + + private func pauseFor(seconds: TimeInterval) { + pausedUntil = Date().addingTimeInterval(seconds) + } + + private func canFlush() -> Bool { + if isFlushing { + return false + } + + if paused { + // We don't flush data if the queue is paused + return false + } + + if pausedUntil != nil, pausedUntil! > Date() { + // We don't flush data if the queue is temporarily paused + return false + } + + return true + } +} diff --git a/PostHog/PostHogSDK.swift b/PostHog/PostHogSDK.swift new file mode 100644 index 000000000..52093fce0 --- /dev/null +++ b/PostHog/PostHogSDK.swift @@ -0,0 +1,766 @@ +// +// PostHogSDK.swift +// PostHogSDK +// +// Created by Ben White on 07.02.23. +// + +import Foundation + +#if os(iOS) || os(tvOS) + import UIKit +#endif + +let retryDelay = 5.0 +let maxRetryDelay = 30.0 + +// renamed to PostHogSDK due to https://github.com/apple/swift/issues/56573 +@objc public class PostHogSDK: NSObject { + private var config: PostHogConfig + + private init(_ config: PostHogConfig) { + self.config = config + } + + private var enabled = false + private let setupLock = NSLock() + private let optOutLock = NSLock() + private let groupsLock = NSLock() + private let personPropsLock = NSLock() + + private var queue: PostHogQueue? + private var api: PostHogApi? + private var storage: PostHogStorage? + private var sessionManager: PostHogSessionManager? + #if !os(watchOS) + private var reachability: Reachability? + #endif + private var flagCallReported = Set() + private var featureFlags: PostHogFeatureFlags? + private var context: PostHogContext? + private static var apiKeys = Set() + private var capturedAppInstalled = false + private var appFromBackground = false + + @objc public static let shared: PostHogSDK = { + let instance = PostHogSDK(PostHogConfig(apiKey: "")) + return instance + }() + + deinit { + #if !os(watchOS) + self.reachability?.stopNotifier() + #endif + } + + @objc public func debug(_ enabled: Bool = true) { + if !isEnabled() { + return + } + + toggleHedgeLog(enabled) + } + + @objc public func setup(_ config: PostHogConfig) { + setupLock.withLock { + if enabled { + hedgeLog("Setup called despite already being setup!") + return + } + + if PostHogSDK.apiKeys.contains(config.apiKey) { + hedgeLog("API Key: ${config.apiKey} already has a PostHog instance.") + } else { + PostHogSDK.apiKeys.insert(config.apiKey) + } + + enabled = true + self.config = config + let theStorage = PostHogStorage(config) + storage = theStorage + let theApi = PostHogApi(config) + api = theApi + featureFlags = PostHogFeatureFlags(config, theStorage, theApi) + sessionManager = PostHogSessionManager(config) + #if !os(watchOS) + do { + reachability = try Reachability() + } catch { + // ignored + } + context = PostHogContext(reachability) + #else + context = PostHogContext() + #endif + + optOutLock.withLock { + let optOut = theStorage.getBool(forKey: .optOut) + config.optOut = optOut ?? config.optOut + } + + #if !os(watchOS) + queue = PostHogQueue(config, theStorage, theApi, reachability) + #else + queue = PostHogQueue(config, theStorage, theApi) + #endif + + queue?.start(disableReachabilityForTesting: config.disableReachabilityForTesting, + disableQueueTimerForTesting: config.disableQueueTimerForTesting) + + registerNotifications() + captureScreenViews() + + DispatchQueue.main.async { + NotificationCenter.default.post(name: PostHogSDK.didStartNotification, object: nil) + } + + if config.preloadFeatureFlags { + reloadFeatureFlags() + } + } + } + + @objc public func getDistinctId() -> String { + if !isEnabled() { + return "" + } + + return sessionManager?.getDistinctId() ?? "" + } + + @objc public func getAnonymousId() -> String { + if !isEnabled() { + return "" + } + + return sessionManager?.getAnonymousId() ?? "" + } + + // EVENT CAPTURE + + private func dynamicContext() -> [String: Any] { + var properties = getRegisteredProperties() + + var groups: [String: String]? + groupsLock.withLock { + groups = getGroups() + } + if groups != nil, !groups!.isEmpty { + properties["$groups"] = groups! + } + + guard let flags = featureFlags?.getFeatureFlags() as? [String: Any] else { + return properties + } + + var keys: [String] = [] + for (key, value) in flags { + properties["$feature/\(key)"] = value + + var active = true + let boolValue = value as? Bool + if boolValue != nil { + active = boolValue! + } else { + active = true + } + + if active { + keys.append(key) + } + } + + if !keys.isEmpty { + properties["$active_feature_flags"] = keys + } + + return properties + } + + private func buildProperties(properties: [String: Any]?, + userProperties: [String: Any]? = nil, + userPropertiesSetOnce: [String: Any]? = nil, + groupProperties: [String: Any]? = nil) -> [String: Any] + { + var props: [String: Any] = [:] + + let staticCtx = context?.staticContext() + let dynamicCtx = context?.dynamicContext() + let localDynamicCtx = dynamicContext() + + if staticCtx != nil { + props = props.merging(staticCtx ?? [:]) { current, _ in current } + } + if dynamicCtx != nil { + props = props.merging(dynamicCtx ?? [:]) { current, _ in current } + } + props = props.merging(localDynamicCtx) { current, _ in current } + if userProperties != nil { + props["$set"] = (userProperties ?? [:]) + } + if userPropertiesSetOnce != nil { + props["$set_once"] = (userPropertiesSetOnce ?? [:]) + } + if groupProperties != nil { + // $groups are also set via the dynamicContext + let currentGroups = props["$groups"] as? [String: Any] ?? [:] + let mergedGroups = currentGroups.merging(groupProperties ?? [:]) { current, _ in current } + props["$groups"] = mergedGroups + } + props = props.merging(properties ?? [:]) { current, _ in current } + + return props + } + + @objc public func flush() { + if !isEnabled() { + return + } + + queue?.flush() + } + + @objc public func reset() { + if !isEnabled() { + return + } + + storage?.reset() + queue?.clear() + flagCallReported.removeAll() + } + + private func getGroups() -> [String: String] { + guard let groups = storage?.getDictionary(forKey: .groups) as? [String: String] else { + return [:] + } + return groups + } + + private func getRegisteredProperties() -> [String: Any] { + guard let props = storage?.getDictionary(forKey: .registerProperties) as? [String: Any] else { + return [:] + } + return props + } + + // register is a reserved word in ObjC + @objc(registerProperties:) + public func register(_ properties: [String: Any]) { + if !isEnabled() { + return + } + + let sanitizedProps = sanitizeDicionary(properties) + if sanitizedProps == nil { + return + } + + personPropsLock.withLock { + let props = getRegisteredProperties() + let mergedProps = props.merging(sanitizedProps!) { _, new in new } + storage?.setDictionary(forKey: .registerProperties, contents: mergedProps) + } + } + + @objc(unregisterProperties:) + public func unregister(_ key: String) { + personPropsLock.withLock { + var props = getRegisteredProperties() + props.removeValue(forKey: key) + storage?.setDictionary(forKey: .registerProperties, contents: props) + } + } + + @objc public func identify(_ distinctId: String) { + identify(distinctId, userProperties: nil, userPropertiesSetOnce: nil) + } + + @objc(identifyWithDistinctId:userProperties:) + public func identify(_ distinctId: String, + userProperties: [String: Any]? = nil) + { + identify(distinctId, userProperties: userProperties, userPropertiesSetOnce: nil) + } + + @objc(identifyWithDistinctId:userProperties:userPropertiesSetOnce:) + public func identify(_ distinctId: String, + userProperties: [String: Any]? = nil, + userPropertiesSetOnce: [String: Any]? = nil) + { + if !isEnabled() { + return + } + + guard let queue = queue, let sessionManager = sessionManager else { + return + } + let oldDistinctId = getDistinctId() + + queue.add(PostHogEvent( + event: "$identify", + distinctId: distinctId, + properties: buildProperties(properties: [ + "distinct_id": distinctId, + "$anon_distinct_id": getAnonymousId(), + ], userProperties: sanitizeDicionary(userProperties), userPropertiesSetOnce: sanitizeDicionary(userPropertiesSetOnce)) + )) + + if distinctId != oldDistinctId { + // We keep the AnonymousId to be used by decide calls and identify to link the previousId + sessionManager.setAnonymousId(oldDistinctId) + sessionManager.setDistinctId(distinctId) + + reloadFeatureFlags() + } + } + + @objc public func capture(_ event: String) { + capture(event, properties: nil, userProperties: nil, userPropertiesSetOnce: nil, groupProperties: nil) + } + + @objc(captureWithEvent:properties:) + public func capture(_ event: String, + properties: [String: Any]? = nil) + { + capture(event, properties: properties, userProperties: nil, userPropertiesSetOnce: nil, groupProperties: nil) + } + + @objc(captureWithEvent:properties:userProperties:) + public func capture(_ event: String, + properties: [String: Any]? = nil, + userProperties: [String: Any]? = nil) + { + capture(event, properties: properties, userProperties: userProperties, userPropertiesSetOnce: nil, groupProperties: nil) + } + + @objc(captureWithEvent:properties:userProperties:userPropertiesSetOnce:) + public func capture(_ event: String, + properties: [String: Any]? = nil, + userProperties: [String: Any]? = nil, + userPropertiesSetOnce: [String: Any]? = nil) + { + capture(event, properties: properties, userProperties: userProperties, userPropertiesSetOnce: userPropertiesSetOnce, groupProperties: nil) + } + + @objc(captureWithEvent:properties:userProperties:userPropertiesSetOnce:groupProperties:) + public func capture(_ event: String, + properties: [String: Any]? = nil, + userProperties: [String: Any]? = nil, + userPropertiesSetOnce: [String: Any]? = nil, + groupProperties: [String: Any]? = nil) + { + if !isEnabled() { + return + } + + guard let queue = queue else { + return + } + queue.add(PostHogEvent( + event: event, + distinctId: getDistinctId(), + properties: buildProperties(properties: sanitizeDicionary(properties), + userProperties: sanitizeDicionary(userProperties), + userPropertiesSetOnce: sanitizeDicionary(userPropertiesSetOnce), + groupProperties: sanitizeDicionary(groupProperties)) + )) + } + + @objc public func screen(_ screenTitle: String) { + screen(screenTitle, properties: nil) + } + + @objc(screenWithTitle:properties:) + public func screen(_ screenTitle: String, properties: [String: Any]? = nil) { + if !isEnabled() { + return + } + + guard let queue = queue else { + return + } + + let props = [ + "$screen_name": screenTitle, + ].merging(sanitizeDicionary(properties) ?? [:]) { prop, _ in prop } + + queue.add(PostHogEvent( + event: "$screen", + distinctId: getDistinctId(), + properties: buildProperties(properties: props) + )) + } + + @objc public func alias(_ alias: String) { + if !isEnabled() { + return + } + + guard let queue = queue else { + return + } + + let props = ["alias": alias] + + queue.add(PostHogEvent( + event: "$create_alias", + distinctId: getDistinctId(), + properties: buildProperties(properties: props) + )) + } + + private func groups(_ newGroups: [String: String]) -> [String: String] { + guard let storage = storage else { + return [:] + } + + var groups: [String: String]? + var mergedGroups: [String: String]? + groupsLock.withLock { + groups = getGroups() + mergedGroups = groups?.merging(newGroups) { _, new in new } + + storage.setDictionary(forKey: .groups, contents: mergedGroups ?? [:]) + } + + var shouldReloadFlags = false + + for (key, value) in newGroups where groups?[key] != value { + shouldReloadFlags = true + break + } + + if shouldReloadFlags { + reloadFeatureFlags() + } + + return mergedGroups ?? [:] + } + + private func groupIdentify(type: String, key: String, groupProperties: [String: Any]? = nil) { + if !isEnabled() { + return + } + + guard let queue = queue else { + return + } + + var props: [String: Any] = ["$group_type": type, + "$group_key": key] + + let groupProps = sanitizeDicionary(groupProperties) + + if groupProps != nil { + props["$group_set"] = groupProps + } + + // Same as .group but without associating the current user with the group + queue.add(PostHogEvent( + event: "$groupidentify", + distinctId: getDistinctId(), + properties: buildProperties(properties: props) + )) + } + + @objc(groupWithType:key:) + public func group(type: String, key: String) { + group(type: type, key: key, groupProperties: nil) + } + + @objc(groupWithType:key:groupProperties:) + public func group(type: String, key: String, groupProperties: [String: Any]? = nil) { + if !isEnabled() { + return + } + + _ = groups([type: key]) + + groupIdentify(type: type, key: key, groupProperties: sanitizeDicionary(groupProperties)) + } + + // FEATURE FLAGS + @objc public func reloadFeatureFlags() { + reloadFeatureFlags { + // No use case + } + } + + @objc(reloadFeatureFlagsWithCallback:) + public func reloadFeatureFlags(_ callback: @escaping () -> Void) { + if !isEnabled() { + return + } + + guard let featureFlags = featureFlags, let sessionManager = sessionManager else { + return + } + + var groups: [String: String]? + groupsLock.withLock { + groups = getGroups() + } + featureFlags.loadFeatureFlags( + distinctId: sessionManager.getDistinctId(), + anonymousId: sessionManager.getAnonymousId(), + groups: groups ?? [:], + callback: callback + ) + } + + @objc public func getFeatureFlag(_ key: String) -> Any? { + if !isEnabled() { + return nil + } + + guard let featureFlags = featureFlags else { + return nil + } + + let value = featureFlags.getFeatureFlag(key) + + if config.sendFeatureFlagEvent { + reportFeatureFlagCalled(flagKey: key, flagValue: value) + } + + return value + } + + @objc public func isFeatureEnabled(_ key: String) -> Bool { + if !isEnabled() { + return false + } + + guard let featureFlags = featureFlags else { + return false + } + + let value = featureFlags.isFeatureEnabled(key) + + if config.sendFeatureFlagEvent { + reportFeatureFlagCalled(flagKey: key, flagValue: value) + } + + return value + } + + @objc public func getFeatureFlagPayload(_ key: String) -> Any? { + if !isEnabled() { + return nil + } + + guard let featureFlags = featureFlags else { + return nil + } + + return featureFlags.getFeatureFlagPayload(key) + } + + private func reportFeatureFlagCalled(flagKey: String, flagValue: Any?) { + if !flagCallReported.contains(flagKey) { + let properties: [String: Any] = [ + "$feature_flag": flagKey, + "$feature_flag_response": flagValue ?? "", + ] + + flagCallReported.insert(flagKey) + + capture("$feature_flag_called", properties: properties) + } + } + + private func isEnabled() -> Bool { + if !enabled { + hedgeLog("PostHog method was called without `setup` being complete. Call wil be ignored.") + } + return enabled + } + + @objc public func optIn() { + if !isEnabled() { + return + } + + optOutLock.withLock { + config.optOut = false + storage?.setBool(forKey: .optOut, contents: false) + } + } + + @objc public func optOut() { + if !isEnabled() { + return + } + + optOutLock.withLock { + config.optOut = true + storage?.setBool(forKey: .optOut, contents: true) + } + } + + @objc public func isOptOut() -> Bool { + if !isEnabled() { + return true + } + + return config.optOut + } + + @objc public func close() { + if !isEnabled() { + return + } + + setupLock.withLock { + enabled = false + PostHogSDK.apiKeys.remove(config.apiKey) + + queue?.stop() + queue = nil + sessionManager = nil + config = PostHogConfig(apiKey: "") + api = nil + #if !os(watchOS) + self.reachability?.stopNotifier() + reachability = nil + #endif + flagCallReported.removeAll() + featureFlags = nil + } + } + + @objc public static func with(_ config: PostHogConfig) -> PostHogSDK { + let postHog = PostHogSDK(config) + postHog.setup(config) + return postHog + } + + private func registerNotifications() { + let defaultCenter = NotificationCenter.default + + #if os(iOS) || os(tvOS) + defaultCenter.addObserver(self, + selector: #selector(captureAppLifecycle), + name: UIApplication.didFinishLaunchingNotification, + object: nil) + defaultCenter.addObserver(self, + selector: #selector(captureAppBackgrounded), + name: UIApplication.didEnterBackgroundNotification, + object: nil) + defaultCenter.addObserver(self, + selector: #selector(captureAppOpenedFromBackground), + name: UIApplication.willEnterForegroundNotification, + object: nil) + #endif + } + + private func captureScreenViews() { + if config.captureScreenViews { + #if os(iOS) || os(tvOS) + UIViewController.swizzleScreenView() + #endif + } + } + + func captureAppInstalled() { + let bundle = Bundle.main + + let versionName = bundle.infoDictionary?["CFBundleShortVersionString"] as? String + let versionCode = bundle.infoDictionary?["CFBundleVersion"] as? String + + // capture app installed/updated + if !capturedAppInstalled { + let userDefaults = UserDefaults.standard + + let previousVersion = userDefaults.string(forKey: "PHGVersionKey") + let previousVersionCode = userDefaults.string(forKey: "PHGBuildKeyV2") + + var props: [String: Any] = [:] + var event: String + if previousVersionCode == nil { + // installed + event = "Application Installed" + } else { + event = "Application Updated" + + // Do not send version updates if its the same + if previousVersionCode == versionCode { + return + } + + if previousVersion != nil { + props["previous_version"] = previousVersion + } + props["previous_build"] = previousVersionCode + } + + var syncDefaults = false + if versionName != nil { + props["version"] = versionName + userDefaults.setValue(versionName, forKey: "PHGVersionKey") + syncDefaults = true + } + + if versionCode != nil { + props["build"] = versionCode + userDefaults.setValue(versionCode, forKey: "PHGBuildKeyV2") + syncDefaults = true + } + + if syncDefaults { + userDefaults.synchronize() + } + + capture(event, properties: props) + + capturedAppInstalled = true + } + } + + func captureAppOpened() { + var props: [String: Any] = [:] + props["from_background"] = false + + let bundle = Bundle.main + + let versionName = bundle.infoDictionary?["CFBundleShortVersionString"] as? String + let versionCode = bundle.infoDictionary?["CFBundleVersion"] as? String + + if versionName != nil { + props["version"] = versionName + } + if versionCode != nil { + props["build"] = versionCode + } + + capture("Application Opened", properties: props) + } + + @objc func captureAppOpenedFromBackground() { + var props: [String: Any] = [:] + props["from_background"] = appFromBackground + + if !appFromBackground { + appFromBackground = true + } + + capture("Application Opened", properties: props) + } + + @objc private func captureAppLifecycle() { + if !config.captureApplicationLifecycleEvents { + return + } + + captureAppInstalled() + captureAppOpened() + } + + @objc func captureAppBackgrounded() { + if !config.captureApplicationLifecycleEvents { + return + } + capture("Application Backgrounded") + } +} diff --git a/PostHog/PostHogSessionManager.swift b/PostHog/PostHogSessionManager.swift new file mode 100644 index 000000000..0c771f524 --- /dev/null +++ b/PostHog/PostHogSessionManager.swift @@ -0,0 +1,65 @@ +// +// PostHogSessionManager.swift +// PostHog +// +// Created by Ben White on 08.02.23. +// + +import Foundation + +class PostHogSessionManager { + private let storage: PostHogStorage! + + private let anonLock = NSLock() + private let distinctLock = NSLock() + init(_ config: PostHogConfig) { + storage = PostHogStorage(config) + } + + public func getAnonymousId() -> String { + var anonymousId: String? + anonLock.withLock { + anonymousId = storage.getString(forKey: .anonymousId) + + if anonymousId == nil { + anonymousId = UUID().uuidString + setAnonId(anonymousId ?? "") + } + } + + return anonymousId ?? "" + } + + public func setAnonymousId(_ id: String) { + anonLock.withLock { + setAnonId(id) + } + } + + private func setAnonId(_ id: String) { + storage.setString(forKey: .anonymousId, contents: id) + } + + public func getDistinctId() -> String { + var distinctId: String? + distinctLock.withLock { + distinctId = storage.getString(forKey: .distinctId) ?? getAnonymousId() + } + return distinctId ?? "" + } + + public func setDistinctId(_ id: String) { + distinctLock.withLock { + storage.setString(forKey: .distinctId, contents: id) + } + } + + public func reset() { + distinctLock.withLock { + storage.remove(key: .distinctId) + } + anonLock.withLock { + storage.remove(key: .anonymousId) + } + } +} diff --git a/PostHog/PostHogStorage.swift b/PostHog/PostHogStorage.swift new file mode 100644 index 000000000..e80754448 --- /dev/null +++ b/PostHog/PostHogStorage.swift @@ -0,0 +1,173 @@ +// +// PostHogStorage.swift +// PostHog +// +// Created by Ben White on 08.02.23. +// + +import Foundation + +/** + # Storage + + posthog-ios stores data either to file or to UserDefaults in order to support tvOS. As recordings won't work on tvOS anyways and we have no tvOS users so far, + we are opting to only support iOS via File storage. + */ + +func applicationSupportDirectoryURL() -> URL { + let url = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first! + return url.appendingPathComponent(Bundle.main.bundleIdentifier!) +} + +class PostHogStorage { + enum StorageKey: String { + case distinctId = "posthog.distinctId" + case anonymousId = "posthog.anonymousId" + case queue = "posthog.queueFolder" // NOTE: This is different to posthog-ios v2 + case oldQeueue = "posthog.queue.plist" + case enabledFeatureFlags = "posthog.enabledFeatureFlags" + case enabledFeatureFlagPayloads = "posthog.enabledFeatureFlagPayloads" + case groups = "posthog.groups" + case registerProperties = "posthog.registerProperties" + case optOut = "posthog.optOut" + } + + private let config: PostHogConfig + + // The location for storing data that we always want to keep + let appFolderUrl: URL + + init(_ config: PostHogConfig) { + self.config = config + + appFolderUrl = applicationSupportDirectoryURL() // .appendingPathComponent(config.apiKey) + + createDirectoryAtURLIfNeeded(url: appFolderUrl) + } + + private func createDirectoryAtURLIfNeeded(url: URL) { + if FileManager.default.fileExists(atPath: url.path) { return } + do { + try FileManager.default.createDirectory(atPath: url.path, withIntermediateDirectories: true) + } catch { + hedgeLog("Error creating storage directory: \(error)") + } + } + + public func url(forKey key: StorageKey) -> URL { + appFolderUrl.appendingPathComponent(key.rawValue) + } + + // The "data" methods are the core for storing data and differ between Modes + // All other typed storage methods call these + private func getData(forKey: StorageKey) -> Data? { + let url = url(forKey: forKey) + + do { + if FileManager.default.fileExists(atPath: url.path) { + return try Data(contentsOf: url) + } + } catch { + hedgeLog("Error reading data from key \(forKey): \(error)") + } + return nil + } + + private func setData(forKey: StorageKey, contents: Data?) { + var url = url(forKey: forKey) + + do { + if contents == nil { + deleteSafely(url) + return + } + + try contents?.write(to: url) + + var resourceValues = URLResourceValues() + resourceValues.isExcludedFromBackup = true + try url.setResourceValues(resourceValues) + } catch { + hedgeLog("Failed to write data for key '\(forKey)' error: \(error)") + } + } + + private func getJson(forKey key: StorageKey) -> Any? { + guard let data = getData(forKey: key) else { return nil } + + do { + return try JSONSerialization.jsonObject(with: data) + } catch { + hedgeLog("Failed to serialize key '\(key)' error: \(error)") + } + return nil + } + + private func setJson(forKey key: StorageKey, json: Any) { + var jsonObject: Any? + + if let dictionary = json as? [AnyHashable: Any] { + jsonObject = dictionary + } else if let array = json as? [Any] { + jsonObject = array + } else { + // TRICKY: This is weird legacy behaviour storing the data as a dictionary + jsonObject = [key.rawValue: json] + } + + var data: Data? + do { + data = try JSONSerialization.data(withJSONObject: jsonObject!) + } catch { + hedgeLog("Failed to serialize key '\(key)' error: \(error)") + } + setData(forKey: key, contents: data) + } + + public func reset() { + deleteSafely(appFolderUrl) + createDirectoryAtURLIfNeeded(url: appFolderUrl) + } + + public func remove(key: StorageKey) { + let url = url(forKey: key) + + deleteSafely(url) + } + + public func getString(forKey key: StorageKey) -> String? { + let value = getJson(forKey: key) + if let stringValue = value as? String { + return stringValue + } else if let dictValue = value as? [String: String] { + return dictValue[key.rawValue] + } + return nil + } + + public func setString(forKey key: StorageKey, contents: String) { + setJson(forKey: key, json: contents) + } + + public func getDictionary(forKey key: StorageKey) -> [AnyHashable: Any]? { + getJson(forKey: key) as? [AnyHashable: Any] + } + + public func setDictionary(forKey key: StorageKey, contents: [AnyHashable: Any]) { + setJson(forKey: key, json: contents) + } + + public func getBool(forKey key: StorageKey) -> Bool? { + let value = getJson(forKey: key) + if let boolValue = value as? Bool { + return boolValue + } else if let dictValue = value as? [String: Bool] { + return dictValue[key.rawValue] + } + return nil + } + + public func setBool(forKey key: StorageKey, contents: Bool) { + setJson(forKey: key, json: contents) + } +} diff --git a/PostHog/PostHogVersion.swift b/PostHog/PostHogVersion.swift new file mode 100644 index 000000000..42f1a0c0e --- /dev/null +++ b/PostHog/PostHogVersion.swift @@ -0,0 +1,11 @@ +// +// PostHogVersion.swift +// PostHog +// +// Created by Manoel Aranda Neto on 13.10.23. +// + +import Foundation + +// if you change this, make sure to also change it in the podspec and check if the script scripts/bump-version.sh still works +let postHogVersion = "3.0.0-alpha.2" diff --git a/PostHog/UIViewController.swift b/PostHog/UIViewController.swift new file mode 100644 index 000000000..087401937 --- /dev/null +++ b/PostHog/UIViewController.swift @@ -0,0 +1,88 @@ +// +// UIViewController.swift +// PostHog +// +// Inspired by +// https://raw.githubusercontent.com/segmentio/analytics-swift/e613e09aa1b97144126a923ec408374f914a6f2e/Examples/other_plugins/UIKitScreenTracking.swift +// +// Created by Manoel Aranda Neto on 23.10.23. +// + +import Foundation +#if os(iOS) || os(tvOS) + import UIKit + + extension UIViewController { + static func swizzle(forClass: AnyClass, original: Selector, new: Selector) { + guard let originalMethod = class_getInstanceMethod(forClass, original) else { return } + guard let swizzledMethod = class_getInstanceMethod(forClass, new) else { return } + method_exchangeImplementations(originalMethod, swizzledMethod) + } + + static func swizzleScreenView() { + UIViewController.swizzle(forClass: UIViewController.self, + original: #selector(UIViewController.viewDidAppear(_:)), + new: #selector(UIViewController.viewDidApperOverride)) + } + + private func activeController() -> UIViewController? { + // if a view is being dismissed, this will return nil + if let root = viewIfLoaded?.window?.rootViewController { + return root + } else if #available(iOS 13.0, *) { + // preferred way to get active controller in ios 13+ + for scene in UIApplication.shared.connectedScenes where scene.activationState == .foregroundActive { + let windowScene = scene as? UIWindowScene + let sceneDelegate = windowScene?.delegate as? UIWindowSceneDelegate + if let target = sceneDelegate, let window = target.window { + return window?.rootViewController + } + } + } else { + // this was deprecated in ios 13.0 + return UIApplication.shared.keyWindow?.rootViewController + } + return nil + } + + private func captureScreenView() { + var rootController = viewIfLoaded?.window?.rootViewController + if rootController == nil { + rootController = activeController() + } + guard let top = findVisibleViewController(activeController()) else { return } + + var name = String(describing: top.classForCoder).replacingOccurrences(of: "ViewController", with: "") + + if name.count == 0 { + name = top.title ?? "Unknown" + } + + if name != "Unknown" { + PostHogSDK.shared.screen(name) + } + } + + @objc func viewDidApperOverride(animated: Bool) { + captureScreenView() + // it looks like we're calling ourselves, but we're actually + // calling the original implementation of viewDidAppear since it's been swizzled. + viewDidApperOverride(animated: animated) + } + + private func findVisibleViewController(_ controller: UIViewController?) -> UIViewController? { + if let navigationController = controller as? UINavigationController { + return findVisibleViewController(navigationController.visibleViewController) + } + if let tabController = controller as? UITabBarController { + if let selected = tabController.selectedViewController { + return findVisibleViewController(selected) + } + } + if let presented = controller?.presentedViewController { + return findVisibleViewController(presented) + } + return controller + } + } +#endif diff --git a/PostHog/Utils/Data+Gzip.swift b/PostHog/Utils/Data+Gzip.swift new file mode 100644 index 000000000..37a76f34f --- /dev/null +++ b/PostHog/Utils/Data+Gzip.swift @@ -0,0 +1,301 @@ +// +// Data+Gzip.swift +// +// https://github.com/1024jp/GzipSwift/blob/731037f6cc2be2ec01562f6597c1d0aa3fe6fd05/Sources/Gzip/Data%2BGzip.swift + +/* + The MIT License (MIT) + + © 2014-2023 1024jp + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +// issues importing scoped classes +// import struct Foundation.Data +import Foundation + +#if os(Linux) + import zlibLinux +#else + import zlib +#endif + +public enum Gzip { + /// Maximum value for windowBits (`MAX_WBITS`) + public static let maxWindowBits = MAX_WBITS +} + +/// Compression level whose rawValue is based on the zlib's constants. +public struct CompressionLevel: RawRepresentable, Sendable { + /// Compression level in the range of `0` (no compression) to `9` (maximum compression). + public let rawValue: Int32 + + public static let noCompression = Self(Z_NO_COMPRESSION) + public static let bestSpeed = Self(Z_BEST_SPEED) + public static let bestCompression = Self(Z_BEST_COMPRESSION) + + public static let defaultCompression = Self(Z_DEFAULT_COMPRESSION) + + public init(rawValue: Int32) { + self.rawValue = rawValue + } + + public init(_ rawValue: Int32) { + self.rawValue = rawValue + } +} + +/// Errors on gzipping/gunzipping based on the zlib error codes. +public struct GzipError: Swift.Error, Sendable { + // cf. http://www.zlib.net/manual.html + + public enum Kind: Equatable, Sendable { + /// The stream structure was inconsistent. + /// + /// - underlying zlib error: `Z_STREAM_ERROR` (-2) + case stream + + /// The input data was corrupted + /// (input stream not conforming to the zlib format or incorrect check value). + /// + /// - underlying zlib error: `Z_DATA_ERROR` (-3) + case data + + /// There was not enough memory. + /// + /// - underlying zlib error: `Z_MEM_ERROR` (-4) + case memory + + /// No progress is possible or there was not enough room in the output buffer. + /// + /// - underlying zlib error: `Z_BUF_ERROR` (-5) + case buffer + + /// The zlib library version is incompatible with the version assumed by the caller. + /// + /// - underlying zlib error: `Z_VERSION_ERROR` (-6) + case version + + /// An unknown error occurred. + /// + /// - parameter code: return error by zlib + case unknown(code: Int) + } + + /// Error kind. + public let kind: Kind + + /// Returned message by zlib. + public let message: String + + init(code: Int32, msg: UnsafePointer?) { + message = msg.flatMap(String.init(validatingUTF8:)) ?? "Unknown gzip error" + kind = Kind(code: code) + } + + public var localizedDescription: String { + message + } +} + +private extension GzipError.Kind { + init(code: Int32) { + switch code { + case Z_STREAM_ERROR: + self = .stream + case Z_DATA_ERROR: + self = .data + case Z_MEM_ERROR: + self = .memory + case Z_BUF_ERROR: + self = .buffer + case Z_VERSION_ERROR: + self = .version + default: + self = .unknown(code: Int(code)) + } + } +} + +public extension Data { + /// Whether the receiver is compressed in gzip format. + var isGzipped: Bool { + starts(with: [0x1F, 0x8B]) // check magic number + } + + /// Create a new `Data` instance by compressing the receiver using zlib. + /// Throws an error if compression failed. + /// + /// The `wBits` parameter allows for managing the size of the history buffer. The possible values are: + /// + /// Value Window size logarithm Input + /// +9 to +15 Base 2 Includes zlib header and trailer + /// -9 to -15 Absolute value of wbits No header and trailer + /// +25 to +31 Low 4 bits of the value Includes gzip header and trailing checksum + /// + /// - Parameter level: Compression level. + /// - Parameter wBits: Manage the size of the history buffer. + /// - Returns: Gzip-compressed `Data` instance. + /// - Throws: `GzipError` + func gzipped(level: CompressionLevel = .defaultCompression, wBits: Int32 = Gzip.maxWindowBits + 16) throws -> Data { + guard !isEmpty else { + return Data() + } + + var stream = z_stream() + var status: Int32 + + status = deflateInit2_(&stream, level.rawValue, Z_DEFLATED, wBits, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY, ZLIB_VERSION, Int32(DataSize.stream)) + + guard status == Z_OK else { + // deflateInit2 returns: + // Z_VERSION_ERROR The zlib library version is incompatible with the version assumed by the caller. + // Z_MEM_ERROR There was not enough memory. + // Z_STREAM_ERROR A parameter is invalid. + + throw GzipError(code: status, msg: stream.msg) + } + + var data = Data(capacity: DataSize.chunk) + repeat { + if Int(stream.total_out) >= data.count { + data.count += DataSize.chunk + } + + let inputCount = count + let outputCount = data.count + + withUnsafeBytes { (inputPointer: UnsafeRawBufferPointer) in + stream.next_in = UnsafeMutablePointer(mutating: inputPointer.bindMemory(to: Bytef.self).baseAddress!).advanced(by: Int(stream.total_in)) + stream.avail_in = uInt(inputCount) - uInt(stream.total_in) + + data.withUnsafeMutableBytes { (outputPointer: UnsafeMutableRawBufferPointer) in + stream.next_out = outputPointer.bindMemory(to: Bytef.self).baseAddress!.advanced(by: Int(stream.total_out)) + stream.avail_out = uInt(outputCount) - uInt(stream.total_out) + + status = deflate(&stream, Z_FINISH) + + stream.next_out = nil + } + + stream.next_in = nil + } + + } while stream.avail_out == 0 && status != Z_STREAM_END + + guard deflateEnd(&stream) == Z_OK, status == Z_STREAM_END else { + throw GzipError(code: status, msg: stream.msg) + } + + data.count = Int(stream.total_out) + + return data + } + + /// Create a new `Data` instance by decompressing the receiver using zlib. + /// Throws an error if decompression failed. + /// + /// The `wBits` parameter allows for managing the size of the history buffer. The possible values are: + /// + /// Value Window size logarithm Input + /// +8 to +15 Base 2 Includes zlib header and trailer + /// -8 to -15 Absolute value of wbits Raw stream with no header and trailer + /// +24 to +31 = 16 + (8 to 15) Low 4 bits of the value Includes gzip header and trailer + /// +40 to +47 = 32 + (8 to 15) Low 4 bits of the value zlib or gzip format + /// + /// - Parameter wBits: Manage the size of the history buffer. + /// - Returns: Gzip-decompressed `Data` instance. + /// - Throws: `GzipError` + func gunzipped(wBits: Int32 = Gzip.maxWindowBits + 32) throws -> Data { + guard !isEmpty else { + return Data() + } + + var data = Data(capacity: count * 2) + var totalIn: uLong = 0 + var totalOut: uLong = 0 + + repeat { + var stream = z_stream() + var status: Int32 + + status = inflateInit2_(&stream, wBits, ZLIB_VERSION, Int32(DataSize.stream)) + + guard status == Z_OK else { + // inflateInit2 returns: + // Z_VERSION_ERROR The zlib library version is incompatible with the version assumed by the caller. + // Z_MEM_ERROR There was not enough memory. + // Z_STREAM_ERROR A parameters are invalid. + + throw GzipError(code: status, msg: stream.msg) + } + + repeat { + if Int(totalOut + stream.total_out) >= data.count { + data.count += count / 2 + } + + let inputCount = count + let outputCount = data.count + + withUnsafeBytes { (inputPointer: UnsafeRawBufferPointer) in + let inputStartPosition = totalIn + stream.total_in + stream.next_in = UnsafeMutablePointer(mutating: inputPointer.bindMemory(to: Bytef.self).baseAddress!).advanced(by: Int(inputStartPosition)) + stream.avail_in = uInt(inputCount) - uInt(inputStartPosition) + + data.withUnsafeMutableBytes { (outputPointer: UnsafeMutableRawBufferPointer) in + let outputStartPosition = totalOut + stream.total_out + stream.next_out = outputPointer.bindMemory(to: Bytef.self).baseAddress!.advanced(by: Int(outputStartPosition)) + stream.avail_out = uInt(outputCount) - uInt(outputStartPosition) + + status = inflate(&stream, Z_SYNC_FLUSH) + + stream.next_out = nil + } + + stream.next_in = nil + } + } while status == Z_OK + + totalIn += stream.total_in + + guard inflateEnd(&stream) == Z_OK, status == Z_STREAM_END else { + // inflate returns: + // Z_DATA_ERROR The input data was corrupted (input stream not conforming to the zlib format or incorrect check value). + // Z_STREAM_ERROR The stream structure was inconsistent (for example if next_in or next_out was NULL). + // Z_MEM_ERROR There was not enough memory. + // Z_BUF_ERROR No progress is possible or there was not enough room in the output buffer when Z_FINISH is used. + throw GzipError(code: status, msg: stream.msg) + } + + totalOut += stream.total_out + + } while totalIn < count + + data.count = Int(totalOut) + + return data + } +} + +private enum DataSize { + static let chunk = 1 << 14 + static let stream = MemoryLayout.size +} diff --git a/PostHog/Utils/DateUtils.swift b/PostHog/Utils/DateUtils.swift new file mode 100644 index 000000000..ce9c11a2d --- /dev/null +++ b/PostHog/Utils/DateUtils.swift @@ -0,0 +1,20 @@ +// +// DateUtils.swift +// PostHog +// +// Created by Manoel Aranda Neto on 27.10.23. +// + +import Foundation + +public func toISO8601String(_ date: Date) -> String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSS'Z'" + return dateFormatter.string(from: date) +} + +public func toISO8601Date(_ date: String) -> Date? { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSS'Z'" + return dateFormatter.date(from: date) +} diff --git a/PostHog/Utils/DictUtils.swift b/PostHog/Utils/DictUtils.swift new file mode 100644 index 000000000..611716079 --- /dev/null +++ b/PostHog/Utils/DictUtils.swift @@ -0,0 +1,43 @@ +// +// DictUtils.swift +// PostHog +// +// Created by Manoel Aranda Neto on 27.10.23. +// + +import Foundation + +public func sanitizeDicionary(_ dict: [String: Any]?) -> [String: Any]? { + if dict == nil || dict!.isEmpty { + return nil + } + + var newDict = dict! + + for (key, value) in newDict where !isValidObject(value) { + if value is URL { + newDict[key] = (value as! URL).absoluteString + continue + } + if value is Date { + newDict[key] = ISO8601DateFormatter().string(from: (value as! Date)) + continue + } + + newDict.removeValue(forKey: key) + hedgeLog("property: \(key) isn't serializable, dropping the item") + } + + return newDict +} + +private func isValidObject(_ object: Any) -> Bool { + if object is String || object is Bool || object is any Numeric || object is NSNumber { + return true + } + if object is [Any?] || object is [String: Any?] { + return JSONSerialization.isValidJSONObject(object) + } + // workaround [object] since isValidJSONObject only accepts an Array or Dict + return JSONSerialization.isValidJSONObject([object]) +} diff --git a/PostHog/Utils/Errors.swift b/PostHog/Utils/Errors.swift new file mode 100644 index 000000000..6a11e1743 --- /dev/null +++ b/PostHog/Utils/Errors.swift @@ -0,0 +1,24 @@ +// +// Errors.swift +// PostHog +// +// Created by Ben White on 21.03.23. +// + +import Foundation + +struct InternalPostHogError: Error, CustomStringConvertible { + let description: String + + init(description: String, fileID: StaticString = #fileID, line: UInt = #line) { + self.description = "\(description) (\(fileID):\(line))" + } +} + +struct FatalPostHogError: Error, CustomStringConvertible { + let description: String + + init(description: String, fileID: StaticString = #fileID, line: UInt = #line) { + self.description = "Fatal PostHog error: \(description) (\(fileID):\(line))" + } +} diff --git a/PostHog/Utils/FileUtils.swift b/PostHog/Utils/FileUtils.swift new file mode 100644 index 000000000..a884e1fab --- /dev/null +++ b/PostHog/Utils/FileUtils.swift @@ -0,0 +1,18 @@ +// +// FileUtils.swift +// PostHog +// +// Created by Manoel Aranda Neto on 30.10.23. +// + +import Foundation + +public func deleteSafely(_ file: URL) { + if FileManager.default.fileExists(atPath: file.path) { + do { + try FileManager.default.removeItem(at: file) + } catch { + hedgeLog("Error trying to delete file \(file.path) \(error)") + } + } +} diff --git a/PostHog/Utils/Hedgelog.swift b/PostHog/Utils/Hedgelog.swift new file mode 100644 index 000000000..f53ad8694 --- /dev/null +++ b/PostHog/Utils/Hedgelog.swift @@ -0,0 +1,20 @@ +// +// Hedgelog.swift +// PostHog +// +// Created by Ben White on 07.02.23. +// + +import Foundation + +var hedgeLogEnabled = false + +func toggleHedgeLog(_ enabled: Bool) { + hedgeLogEnabled = enabled +} + +// Meant for internally logging PostHog related things +func hedgeLog(_ message: String) { + if !hedgeLogEnabled { return } + print("[PostHog] \(message)") +} diff --git a/PostHog/Utils/Reachability.swift b/PostHog/Utils/Reachability.swift new file mode 100644 index 000000000..ea6cae487 --- /dev/null +++ b/PostHog/Utils/Reachability.swift @@ -0,0 +1,418 @@ +/* + Copyright (c) 2014, Ashley Mills + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + */ + +import Foundation + +#if !os(watchOS) + import SystemConfiguration + + public enum ReachabilityError: Error { + case failedToCreateWithAddress(sockaddr, Int32) + case failedToCreateWithHostname(String, Int32) + case unableToSetCallback(Int32) + case unableToSetDispatchQueue(Int32) + case unableToGetFlags(Int32) + } + + @available(*, unavailable, renamed: "Notification.Name.reachabilityChanged") + public let ReachabilityChangedNotification = NSNotification.Name("ReachabilityChangedNotification") + + public extension Notification.Name { + static let reachabilityChanged = Notification.Name("reachabilityChanged") + } + + public class Reachability { + public typealias NetworkReachable = (Reachability) -> Void + public typealias NetworkUnreachable = (Reachability) -> Void + + @available(*, unavailable, renamed: "Connection") + public enum NetworkStatus: CustomStringConvertible { + case notReachable, reachableViaWiFi, reachableViaWWAN + public var description: String { + switch self { + case .reachableViaWWAN: return "Cellular" + case .reachableViaWiFi: return "WiFi" + case .notReachable: return "No Connection" + } + } + } + + public enum Connection: CustomStringConvertible { + case unavailable, wifi, cellular + public var description: String { + switch self { + case .cellular: return "Cellular" + case .wifi: return "WiFi" + case .unavailable: return "No Connection" + } + } + + @available(*, deprecated, renamed: "unavailable") + public static let none: Connection = .unavailable + } + + public var whenReachable: NetworkReachable? + public var whenUnreachable: NetworkUnreachable? + + @available(*, deprecated, renamed: "allowsCellularConnection") + public let reachableOnWWAN: Bool = true + + /// Set to `false` to force Reachability.connection to .none when on cellular connection (default value `true`) + public var allowsCellularConnection: Bool + + // The notification center on which "reachability changed" events are being posted + public var notificationCenter: NotificationCenter = .default + + @available(*, deprecated, renamed: "connection.description") + public var currentReachabilityString: String { + "\(connection)" + } + + @available(*, unavailable, renamed: "connection") + public var currentReachabilityStatus: Connection { + connection + } + + public var connection: Connection { + if flags == nil { + try? setReachabilityFlags() + } + + switch flags?.connection { + case .unavailable?, nil: return .unavailable + case .cellular?: return allowsCellularConnection ? .cellular : .unavailable + case .wifi?: return .wifi + } + } + + private var isRunningOnDevice: Bool = { + #if targetEnvironment(simulator) + return false + #else + return true + #endif + }() + + private(set) var notifierRunning = false + private let reachabilityRef: SCNetworkReachability + private let reachabilitySerialQueue: DispatchQueue + private let notificationQueue: DispatchQueue? + private(set) var flags: SCNetworkReachabilityFlags? { + didSet { + guard flags != oldValue else { return } + notifyReachabilityChanged() + } + } + + public required init(reachabilityRef: SCNetworkReachability, + queueQoS: DispatchQoS = .default, + targetQueue: DispatchQueue? = nil, + notificationQueue: DispatchQueue? = .main) + { + allowsCellularConnection = true + self.reachabilityRef = reachabilityRef + reachabilitySerialQueue = DispatchQueue(label: "uk.co.ashleymills.reachability", qos: queueQoS, target: targetQueue) + self.notificationQueue = notificationQueue + } + + public convenience init(hostname: String, + queueQoS: DispatchQoS = .default, + targetQueue: DispatchQueue? = nil, + notificationQueue: DispatchQueue? = .main) throws + { + guard let ref = SCNetworkReachabilityCreateWithName(nil, hostname) else { + throw ReachabilityError.failedToCreateWithHostname(hostname, SCError()) + } + self.init(reachabilityRef: ref, queueQoS: queueQoS, targetQueue: targetQueue, notificationQueue: notificationQueue) + } + + public convenience init(queueQoS: DispatchQoS = .default, + targetQueue: DispatchQueue? = nil, + notificationQueue: DispatchQueue? = .main) throws + { + var zeroAddress = sockaddr() + zeroAddress.sa_len = UInt8(MemoryLayout.size) + zeroAddress.sa_family = sa_family_t(AF_INET) + + guard let ref = SCNetworkReachabilityCreateWithAddress(nil, &zeroAddress) else { + throw ReachabilityError.failedToCreateWithAddress(zeroAddress, SCError()) + } + + self.init(reachabilityRef: ref, queueQoS: queueQoS, targetQueue: targetQueue, notificationQueue: notificationQueue) + } + + deinit { + stopNotifier() + } + } + + public extension Reachability { + // MARK: - *** Notifier methods *** + + func startNotifier() throws { + guard !notifierRunning else { return } + + let callback: SCNetworkReachabilityCallBack = { _, flags, info in + guard let info = info else { return } + + // `weakifiedReachability` is guaranteed to exist by virtue of our + // retain/release callbacks which we provided to the `SCNetworkReachabilityContext`. + let weakifiedReachability = Unmanaged.fromOpaque(info).takeUnretainedValue() + + // The weak `reachability` _may_ no longer exist if the `Reachability` + // object has since been deallocated but a callback was already in flight. + weakifiedReachability.reachability?.flags = flags + } + + let weakifiedReachability = ReachabilityWeakifier(reachability: self) + let opaqueWeakifiedReachability = Unmanaged.passUnretained(weakifiedReachability).toOpaque() + + var context = SCNetworkReachabilityContext( + version: 0, + info: UnsafeMutableRawPointer(opaqueWeakifiedReachability), + retain: { (info: UnsafeRawPointer) -> UnsafeRawPointer in + let unmanagedWeakifiedReachability = Unmanaged.fromOpaque(info) + _ = unmanagedWeakifiedReachability.retain() + return UnsafeRawPointer(unmanagedWeakifiedReachability.toOpaque()) + }, + release: { (info: UnsafeRawPointer) in + let unmanagedWeakifiedReachability = Unmanaged.fromOpaque(info) + unmanagedWeakifiedReachability.release() + }, + copyDescription: { (info: UnsafeRawPointer) -> Unmanaged in + let unmanagedWeakifiedReachability = Unmanaged.fromOpaque(info) + let weakifiedReachability = unmanagedWeakifiedReachability.takeUnretainedValue() + let description = weakifiedReachability.reachability?.description ?? "nil" + return Unmanaged.passRetained(description as CFString) + } + ) + + if !SCNetworkReachabilitySetCallback(reachabilityRef, callback, &context) { + stopNotifier() + throw ReachabilityError.unableToSetCallback(SCError()) + } + + if !SCNetworkReachabilitySetDispatchQueue(reachabilityRef, reachabilitySerialQueue) { + stopNotifier() + throw ReachabilityError.unableToSetDispatchQueue(SCError()) + } + + // Perform an initial check + try setReachabilityFlags() + + notifierRunning = true + } + + func stopNotifier() { + defer { notifierRunning = false } + + SCNetworkReachabilitySetCallback(reachabilityRef, nil, nil) + SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil) + } + + // MARK: - *** Connection test methods *** + + @available(*, deprecated, message: "Please use `connection != .none`") + var isReachable: Bool { + connection != .unavailable + } + + @available(*, deprecated, message: "Please use `connection == .cellular`") + var isReachableViaWWAN: Bool { + // Check we're not on the simulator, we're REACHABLE and check we're on WWAN + connection == .cellular + } + + @available(*, deprecated, message: "Please use `connection == .wifi`") + var isReachableViaWiFi: Bool { + connection == .wifi + } + + var description: String { + flags?.description ?? "unavailable flags" + } + } + + private extension Reachability { + func setReachabilityFlags() throws { + try reachabilitySerialQueue.sync { [unowned self] in + var flags = SCNetworkReachabilityFlags() + if !SCNetworkReachabilityGetFlags(reachabilityRef, &flags) { + stopNotifier() + throw ReachabilityError.unableToGetFlags(SCError()) + } + + self.flags = flags + } + } + + func notifyReachabilityChanged() { + let notify = { [weak self] in + guard let self = self else { return } + self.connection != .unavailable ? self.whenReachable?(self) : self.whenUnreachable?(self) + self.notificationCenter.post(name: .reachabilityChanged, object: self) + } + + // notify on the configured `notificationQueue`, or the caller's (i.e. `reachabilitySerialQueue`) + notificationQueue?.async(execute: notify) ?? notify() + } + } + + extension SCNetworkReachabilityFlags { + typealias Connection = Reachability.Connection + + var connection: Connection { + guard isReachableFlagSet else { return .unavailable } + + // If we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi + #if targetEnvironment(simulator) + return .wifi + #else + var connection = Connection.unavailable + + if !isConnectionRequiredFlagSet { + connection = .wifi + } + + if isConnectionOnTrafficOrDemandFlagSet { + if !isInterventionRequiredFlagSet { + connection = .wifi + } + } + + if isOnWWANFlagSet { + connection = .cellular + } + + return connection + #endif + } + + var isOnWWANFlagSet: Bool { + #if os(iOS) + return contains(.isWWAN) + #else + return false + #endif + } + + var isReachableFlagSet: Bool { + contains(.reachable) + } + + var isConnectionRequiredFlagSet: Bool { + contains(.connectionRequired) + } + + var isInterventionRequiredFlagSet: Bool { + contains(.interventionRequired) + } + + var isConnectionOnTrafficFlagSet: Bool { + contains(.connectionOnTraffic) + } + + var isConnectionOnDemandFlagSet: Bool { + contains(.connectionOnDemand) + } + + var isConnectionOnTrafficOrDemandFlagSet: Bool { + !intersection([.connectionOnTraffic, .connectionOnDemand]).isEmpty + } + + var isTransientConnectionFlagSet: Bool { + contains(.transientConnection) + } + + var isLocalAddressFlagSet: Bool { + contains(.isLocalAddress) + } + + var isDirectFlagSet: Bool { + contains(.isDirect) + } + + var isConnectionRequiredAndTransientFlagSet: Bool { + intersection([.connectionRequired, .transientConnection]) == [.connectionRequired, .transientConnection] + } + + var description: String { + let W = isOnWWANFlagSet ? "W" : "-" + let R = isReachableFlagSet ? "R" : "-" + let c = isConnectionRequiredFlagSet ? "c" : "-" + let t = isTransientConnectionFlagSet ? "t" : "-" + let i = isInterventionRequiredFlagSet ? "i" : "-" + let C = isConnectionOnTrafficFlagSet ? "C" : "-" + let D = isConnectionOnDemandFlagSet ? "D" : "-" + let l = isLocalAddressFlagSet ? "l" : "-" + let d = isDirectFlagSet ? "d" : "-" + + return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)" + } + } + + /** + `ReachabilityWeakifier` weakly wraps the `Reachability` class + in order to break retain cycles when interacting with CoreFoundation. + + CoreFoundation callbacks expect a pair of retain/release whenever an + opaque `info` parameter is provided. These callbacks exist to guard + against memory management race conditions when invoking the callbacks. + + #### Race Condition + + If we passed `SCNetworkReachabilitySetCallback` a direct reference to our + `Reachability` class without also providing corresponding retain/release + callbacks, then a race condition can lead to crashes when: + - `Reachability` is deallocated on thread X + - A `SCNetworkReachability` callback(s) is already in flight on thread Y + + #### Retain Cycle + + If we pass `Reachability` to CoreFoundtion while also providing retain/ + release callbacks, we would create a retain cycle once CoreFoundation + retains our `Reachability` class. This fixes the crashes and his how + CoreFoundation expects the API to be used, but doesn't play nicely with + Swift/ARC. This cycle would only be broken after manually calling + `stopNotifier()` — `deinit` would never be called. + + #### ReachabilityWeakifier + + By providing both retain/release callbacks and wrapping `Reachability` in + a weak wrapper, we: + - interact correctly with CoreFoundation, thereby avoiding a crash. + See "Memory Management Programming Guide for Core Foundation". + - don't alter the public API of `Reachability.swift` in any way + - still allow for automatic stopping of the notifier on `deinit`. + */ + private class ReachabilityWeakifier { + weak var reachability: Reachability? + init(reachability: Reachability) { + self.reachability = reachability + } + } +#endif diff --git a/PostHog/Utils/ReadWriteLock.swift b/PostHog/Utils/ReadWriteLock.swift new file mode 100644 index 000000000..cd7d05934 --- /dev/null +++ b/PostHog/Utils/ReadWriteLock.swift @@ -0,0 +1,64 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import Foundation + +/// A property wrapper using a fair, POSIX conforming reader-writer lock for atomic +/// access to the value. It is optimised for concurrent reads and exclusive writes. +/// +/// The wrapper is a class to prevent copying the lock, it creates and initilaizes a `pthread_rwlock_t`. +/// An additional method `mutate` allow to safely mutate the value in-place (to read it +/// and write it while obtaining the lock only once). +@propertyWrapper +public final class ReadWriteLock { + /// The wrapped value. + private var value: Value + + /// The lock object. + private var rwlock = pthread_rwlock_t() + + public init(wrappedValue value: Value) { + pthread_rwlock_init(&rwlock, nil) + self.value = value + } + + deinit { + pthread_rwlock_destroy(&rwlock) + } + + /// The wrapped value. + /// + /// The `get` will acquire the lock for reading while the `set` will acquire for + /// writing. + public var wrappedValue: Value { + get { + pthread_rwlock_rdlock(&rwlock) + defer { pthread_rwlock_unlock(&rwlock) } + return value + } + set { + pthread_rwlock_wrlock(&rwlock) + value = newValue + pthread_rwlock_unlock(&rwlock) + } + } + + /// Provides a non-escaping closure for mutation. + /// The lock will be acquired once for writing before invoking the closure. + /// + /// - Parameter closure: The closure with the mutable value. + public func mutate(_ closure: (inout Value) -> Void) { + pthread_rwlock_wrlock(&rwlock) + closure(&value) + pthread_rwlock_unlock(&rwlock) + } +} + +func synchronized(_ lock: Any, closure: () -> Void) { + objc_sync_enter(lock) + closure() + objc_sync_exit(lock) +} diff --git a/PostHog/Vendor/ObjC.h b/PostHog/Vendor/ObjC.h deleted file mode 100644 index 45e7658ca..000000000 --- a/PostHog/Vendor/ObjC.h +++ /dev/null @@ -1,7 +0,0 @@ -#import - -@interface ObjC : NSObject - -+ (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error; - -@end \ No newline at end of file diff --git a/PostHog/Vendor/ObjC.m b/PostHog/Vendor/ObjC.m deleted file mode 100644 index 209785bdb..000000000 --- a/PostHog/Vendor/ObjC.m +++ /dev/null @@ -1,16 +0,0 @@ -#import "ObjC.h" - -@implementation ObjC - -+ (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error { - @try { - tryBlock(); - return YES; - } - @catch (NSException *exception) { - *error = [[NSError alloc] initWithDomain:exception.name code:0 userInfo:exception.userInfo]; - return NO; - } -} - -@end \ No newline at end of file diff --git a/PostHog/Vendor/PHGReachability.h b/PostHog/Vendor/PHGReachability.h deleted file mode 100644 index 66c5d0d90..000000000 --- a/PostHog/Vendor/PHGReachability.h +++ /dev/null @@ -1,104 +0,0 @@ -/* - Copyright (c) 2011, Tony Million. - All rights reserved. - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - 1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - */ - -#import -#import - -/** - * Does ARC support GCD objects? - * It does if the minimum deployment target is iOS 6+ or Mac OS X 8+ - * - * @see http://opensource.apple.com/source/libdispatch/libdispatch-228.18/os/object.h - **/ -#if OS_OBJECT_USE_OBJC -#define NEEDS_DISPATCH_RETAIN_RELEASE 0 -#else -#define NEEDS_DISPATCH_RETAIN_RELEASE 1 -#endif - -/** - * Create NS_ENUM macro if it does not exist on the targeted version of iOS or OS X. - * - * @see http://nshipster.com/ns_enum-ns_options/ - **/ -#ifndef NS_ENUM -#define NS_ENUM(_type, _name) \ - enum _name : _type _name; \ - enum _name : _type -#endif - -NS_ASSUME_NONNULL_BEGIN - -extern NSString *const kPHGReachabilityChangedNotification; - -typedef NS_ENUM(NSInteger, PHGNetworkStatus) { - // Apple NetworkStatus Compatible Names. - PHGNotReachable = 0, - PHGReachableViaWiFi = 2, - PHGReachableViaWWAN = 1 -}; - -@class PHGReachability; - -typedef void (^PHGNetworkReachable)(PHGReachability *reachability); -typedef void (^PHGNetworkUnreachable)(PHGReachability *reachability); - - -@interface PHGReachability : NSObject - -@property (nonatomic, copy, nullable) PHGNetworkReachable reachableBlock; -@property (nonatomic, copy, nullable) PHGNetworkUnreachable unreachableBlock; - - -@property (nonatomic, assign) BOOL reachableOnWWAN; - -+ (PHGReachability *_Nullable)reachabilityWithHostname:(NSString *)hostname; -+ (PHGReachability *_Nullable)reachabilityForInternetConnection; -+ (PHGReachability *_Nullable)reachabilityForLocalWiFi; - -- (PHGReachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref; - -- (BOOL)startNotifier; -- (void)stopNotifier; - -- (BOOL)isReachable; -- (BOOL)isReachableViaWWAN; -- (BOOL)isReachableViaWiFi; - -// WWAN may be available, but not active until a connection has been established. -// WiFi may require a connection for VPN on Demand. -- (BOOL)isConnectionRequired; // Identical DDG variant. -- (BOOL)connectionRequired; // Apple's routine. -// Dynamic, on demand connection? -- (BOOL)isConnectionOnDemand; -// Is user intervention required? -- (BOOL)isInterventionRequired; - -- (PHGNetworkStatus)currentReachabilityStatus; -- (SCNetworkReachabilityFlags)reachabilityFlags; -- (NSString *)currentReachabilityString; -- (NSString *)currentReachabilityFlags; - -@end - -NS_ASSUME_NONNULL_END diff --git a/PostHog/Vendor/PHGReachability.m b/PostHog/Vendor/PHGReachability.m deleted file mode 100644 index 901cf4386..000000000 --- a/PostHog/Vendor/PHGReachability.m +++ /dev/null @@ -1,490 +0,0 @@ -/* - Copyright (c) 2011, Tony Million. - All rights reserved. - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - 1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - */ - -#import -#import -#import -#import -#import -#import -#import "PHGReachability.h" - - -NSString *const kPHGReachabilityChangedNotification = @"kPHGReachabilityChangedNotification"; - - -@interface PHGReachability () - -@property (nonatomic, assign) SCNetworkReachabilityRef reachabilityRef; - - -#if NEEDS_DISPATCH_RETAIN_RELEASE -@property (nonatomic, assign) dispatch_queue_t reachabilitySerialQueue; -#else -@property (nonatomic, strong) dispatch_queue_t reachabilitySerialQueue; -#endif - - -@property (nonatomic, strong) id reachabilityObject; - -- (void)reachabilityChanged:(SCNetworkReachabilityFlags)flags; -- (BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags; - -@end - -static NSString *reachabilityFlags(SCNetworkReachabilityFlags flags) -{ - return [NSString stringWithFormat:@"%c%c %c%c%c%c%c%c%c", -#if TARGET_OS_IPHONE - (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', -#else - 'X', -#endif - (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', - (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', - (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', - (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', - (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', - (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', - (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', - (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-']; -} - -// Start listening for reachability notifications on the current run loop -static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info) -{ -#pragma unused(target) -#if __has_feature(objc_arc) - PHGReachability *reachability = ((__bridge PHGReachability *)info); -#else - PHGReachability *reachability = ((PHGReachability *)info); -#endif - - // We probably don't need an autoreleasepool here, as GCD docs state each queue has its own autorelease pool, - // but what the heck eh? - @autoreleasepool - { - [reachability reachabilityChanged:flags]; - } -} - - -@implementation PHGReachability - -@synthesize reachabilityRef; -@synthesize reachabilitySerialQueue; - -@synthesize reachableOnWWAN; - -@synthesize reachableBlock; -@synthesize unreachableBlock; - -@synthesize reachabilityObject; - -#pragma mark - Class Constructor Methods - -+ (PHGReachability *)reachabilityWithHostName:(NSString *)hostname -{ - return [PHGReachability reachabilityWithHostname:hostname]; -} - -+ (PHGReachability *)reachabilityWithHostname:(NSString *)hostname -{ - SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostname UTF8String]); - if (ref) { - id reachability = [[self alloc] initWithReachabilityRef:ref]; - CFRelease(ref); - -#if __has_feature(objc_arc) - return reachability; -#else - return [reachability autorelease]; -#endif - } - - return nil; -} - -+ (PHGReachability *)reachabilityWithAddress:(const struct sockaddr_in *)hostAddress -{ - SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)hostAddress); - if (ref) { - id reachability = [[self alloc] initWithReachabilityRef:ref]; - CFRelease(ref); - -#if __has_feature(objc_arc) - return reachability; -#else - return [reachability autorelease]; -#endif - } - - return nil; -} - -+ (PHGReachability *)reachabilityForInternetConnection -{ - struct sockaddr_in zeroAddress; - bzero(&zeroAddress, sizeof(zeroAddress)); - zeroAddress.sin_len = sizeof(zeroAddress); - zeroAddress.sin_family = AF_INET; - - return [self reachabilityWithAddress:&zeroAddress]; -} - -+ (PHGReachability *)reachabilityForLocalWiFi -{ - struct sockaddr_in localWifiAddress; - bzero(&localWifiAddress, sizeof(localWifiAddress)); - localWifiAddress.sin_len = sizeof(localWifiAddress); - localWifiAddress.sin_family = AF_INET; - // IN_LINKLOCALNETNUM is defined in as 169.254.0.0 - localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); - - return [self reachabilityWithAddress:&localWifiAddress]; -} - - -// Initialization methods - -- (PHGReachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref -{ - self = [super init]; - if (self != nil) { - self.reachableOnWWAN = YES; - self.reachabilityRef = ref; - CFRetain(self.reachabilityRef); - } - - return self; -} - -- (void)dealloc -{ - [self stopNotifier]; - - if (self.reachabilityRef) { - CFRelease(self.reachabilityRef); - self.reachabilityRef = nil; - } - - self.reachableBlock = nil; - self.unreachableBlock = nil; - -#if !(__has_feature(objc_arc)) - [super dealloc]; -#endif -} - -#pragma mark - Notifier Methods - -// Notifier -// NOTE: This uses GCD to trigger the blocks - they *WILL NOT* be called on THE MAIN THREAD -// - In other words DO NOT DO ANY UI UPDATES IN THE BLOCKS. -// INSTEAD USE dispatch_async(dispatch_get_main_queue(), ^{UISTUFF}) (or dispatch_sync if you want) - -- (BOOL)startNotifier -{ - SCNetworkReachabilityContext context = {0, NULL, NULL, NULL, NULL}; - - // this should do a retain on ourself, so as long as we're in notifier mode we shouldn't disappear out from under ourselves - // woah - self.reachabilityObject = self; - - - // First, we need to create a serial queue. - // We allocate this once for the lifetime of the notifier. - self.reachabilitySerialQueue = dispatch_queue_create("com.tonymillion.reachability", NULL); - if (!self.reachabilitySerialQueue) { - return NO; - } - -#if __has_feature(objc_arc) - context.info = (__bridge void *)self; -#else - context.info = (void *)self; -#endif - - if (!SCNetworkReachabilitySetCallback(self.reachabilityRef, TMReachabilityCallback, &context)) { -#ifdef DEBUG - NSLog(@"SCNetworkReachabilitySetCallback() failed: %s", SCErrorString(SCError())); -#endif - - // Clear out the dispatch queue - if (self.reachabilitySerialQueue) { -#if NEEDS_DISPATCH_RETAIN_RELEASE - dispatch_release(self.reachabilitySerialQueue); -#endif - self.reachabilitySerialQueue = nil; - } - - self.reachabilityObject = nil; - - return NO; - } - - // Set it as our reachability queue, which will retain the queue - if (!SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, self.reachabilitySerialQueue)) { -#ifdef DEBUG - NSLog(@"SCNetworkReachabilitySetDispatchQueue() failed: %s", SCErrorString(SCError())); -#endif - - // UH OH - FAILURE! - - // First stop, any callbacks! - SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL); - - // Then clear out the dispatch queue. - if (self.reachabilitySerialQueue) { -#if NEEDS_DISPATCH_RETAIN_RELEASE - dispatch_release(self.reachabilitySerialQueue); -#endif - self.reachabilitySerialQueue = nil; - } - - self.reachabilityObject = nil; - - return NO; - } - - return YES; -} - -- (void)stopNotifier -{ - // First stop, any callbacks! - SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL); - - // Unregister target from the GCD serial dispatch queue. - SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, NULL); - - if (self.reachabilitySerialQueue) { -#if NEEDS_DISPATCH_RETAIN_RELEASE - dispatch_release(self.reachabilitySerialQueue); -#endif - self.reachabilitySerialQueue = nil; - } - - self.reachabilityObject = nil; -} - -#pragma mark - reachability tests - -// This is for the case where you flick the airplane mode; -// you end up getting something like this: -//PHGReachability: WR ct----- -//PHGReachability: -- ------- -//PHGReachability: WR ct----- -//PHGReachability: -- ------- -// We treat this as 4 UNREACHABLE triggers - really apple should do better than this - -#define testcase (kSCNetworkReachabilityFlagsConnectionRequired | kSCNetworkReachabilityFlagsTransientConnection) - -- (BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags -{ - BOOL connectionUP = YES; - - if (!(flags & kSCNetworkReachabilityFlagsReachable)) - connectionUP = NO; - - if ((flags & testcase) == testcase) - connectionUP = NO; - -#if TARGET_OS_IPHONE - if (flags & kSCNetworkReachabilityFlagsIsWWAN) { - // We're on 3G. - if (!self.reachableOnWWAN) { - // We don't want to connect when on 3G. - connectionUP = NO; - } - } -#endif - - return connectionUP; -} - -- (BOOL)isReachable -{ - SCNetworkReachabilityFlags flags; - - if (!SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) - return NO; - - return [self isReachableWithFlags:flags]; -} - -- (BOOL)isReachableViaWWAN -{ -#if TARGET_OS_IPHONE - - SCNetworkReachabilityFlags flags = 0; - - if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { - // Check we're REACHABLE - if (flags & kSCNetworkReachabilityFlagsReachable) { - // Now, check we're on WWAN - if (flags & kSCNetworkReachabilityFlagsIsWWAN) { - return YES; - } - } - } -#endif - - return NO; -} - -- (BOOL)isReachableViaWiFi -{ - SCNetworkReachabilityFlags flags = 0; - - if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { - // Check we're reachable - if ((flags & kSCNetworkReachabilityFlagsReachable)) { -#if TARGET_OS_IPHONE - // Check we're NOT on WWAN - if ((flags & kSCNetworkReachabilityFlagsIsWWAN)) { - return NO; - } -#endif - return YES; - } - } - - return NO; -} - - -// WWAN may be available, but not active until a connection has been established. -// WiFi may require a connection for VPN on Demand. -- (BOOL)isConnectionRequired -{ - return [self connectionRequired]; -} - -- (BOOL)connectionRequired -{ - SCNetworkReachabilityFlags flags; - - if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { - return (flags & kSCNetworkReachabilityFlagsConnectionRequired); - } - - return NO; -} - -// Dynamic, on demand connection? -- (BOOL)isConnectionOnDemand -{ - SCNetworkReachabilityFlags flags; - - if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { - return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) && - (flags & (kSCNetworkReachabilityFlagsConnectionOnTraffic | kSCNetworkReachabilityFlagsConnectionOnDemand))); - } - - return NO; -} - -// Is user intervention required? -- (BOOL)isInterventionRequired -{ - SCNetworkReachabilityFlags flags; - - if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { - return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) && - (flags & kSCNetworkReachabilityFlagsInterventionRequired)); - } - - return NO; -} - - -#pragma mark - reachability status stuff - -- (PHGNetworkStatus)currentReachabilityStatus -{ - if ([self isReachable]) { - if ([self isReachableViaWiFi]) - return PHGReachableViaWiFi; - -#if TARGET_OS_IPHONE - return PHGReachableViaWWAN; -#endif - } - - return PHGNotReachable; -} - -- (SCNetworkReachabilityFlags)reachabilityFlags -{ - SCNetworkReachabilityFlags flags = 0; - - if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) { - return flags; - } - - return 0; -} - -- (NSString *)currentReachabilityString -{ - PHGNetworkStatus temp = [self currentReachabilityStatus]; - - if (temp == reachableOnWWAN) { - // Updated for the fact that we have CDMA phones now! - return NSLocalizedString(@"Cellular", @""); - } - if (temp == PHGReachableViaWiFi) { - return NSLocalizedString(@"WiFi", @""); - } - - return NSLocalizedString(@"No Connection", @""); -} - -- (NSString *)currentReachabilityFlags -{ - return reachabilityFlags([self reachabilityFlags]); -} - -#pragma mark - Callback function calls this method - -- (void)reachabilityChanged:(SCNetworkReachabilityFlags)flags -{ - if ([self isReachableWithFlags:flags]) { - if (self.reachableBlock) { - self.reachableBlock(self); - } - } else { - if (self.unreachableBlock) { - self.unreachableBlock(self); - } - } - - // this makes sure the change notification happens on the MAIN THREAD - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:kPHGReachabilityChangedNotification - object:self]; - }); -} - -@end diff --git a/PostHogExample/Api.swift b/PostHogExample/Api.swift new file mode 100644 index 000000000..b5070cee4 --- /dev/null +++ b/PostHogExample/Api.swift @@ -0,0 +1,54 @@ +// +// Api.swift +// PostHogExample +// +// Created by Ben White on 06.02.23. +// + +import Foundation +import OSLog + +struct PostHogBeerInfo: Codable, Identifiable { + let id: Int + var name: String + var first_brewed: String +} + +class Api: ObservableObject { + @Published var beers = [PostHogBeerInfo]() + + var logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "main") + + func listBeers(completion: @escaping ([PostHogBeerInfo]) -> Void) { + guard let url = URL(string: "https://api.punkapi.com/v2/beers") else { + return + } + + logger.info("Requesting beers list...") + URLSession.shared.dataTask(with: url) { data, _, _ in + let beers = try! JSONDecoder().decode([PostHogBeerInfo].self, from: data!) + + DispatchQueue.main.async { + completion(beers) + } + }.resume() + } + + func failingRequest() -> URLSessionDataTask? { + guard let url = URL(string: "https://api.github.com/user") else { + return nil + } + + logger.info("Requesting protected endpoint...") + let task = URLSession.shared.dataTask(with: url) { data, _, _ in + if data == nil { + return + } + print("Response", String(decoding: data!, as: UTF8.self)) + } + + task.resume() + + return task + } +} diff --git a/PostHogExample/AppDelegate.swift b/PostHogExample/AppDelegate.swift new file mode 100644 index 000000000..a5f09b138 --- /dev/null +++ b/PostHogExample/AppDelegate.swift @@ -0,0 +1,39 @@ +// +// AppDelegate.swift +// PostHogExample +// +// Created by Ben White on 10.01.23. +// + +import Foundation +import PostHog +import UIKit + +class AppDelegate: NSObject, UIApplicationDelegate { + func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { + let config = PostHogConfig( + apiKey: "_6SG-F7I1vCuZ-HdJL3VZQqjBlaSb1_20hDPwqMNnGI" + ) + // the ScreenViews for SwiftUI does not work, the names are not useful + config.captureScreenViews = false + + PostHogSDK.shared.setup(config) +// PostHogSDK.shared.debug() +// PostHogSDK.shared.capture("App started!") + + let defaultCenter = NotificationCenter.default + + #if os(iOS) || os(tvOS) + defaultCenter.addObserver(self, + selector: #selector(receiveFeatureFlags), + name: PostHogSDK.didReceiveFeatureFlags, + object: nil) + #endif + + return true + } + + @objc func receiveFeatureFlags() { + print("receiveFeatureFlags") + } +} diff --git a/PostHogExample/Assets.xcassets/AccentColor.colorset/Contents.json b/PostHogExample/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000..eb8789700 --- /dev/null +++ b/PostHogExample/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PostHogExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/PostHogExample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..13613e3ee --- /dev/null +++ b/PostHogExample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PostHogExample/Assets.xcassets/Contents.json b/PostHogExample/Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/PostHogExample/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PostHogExample/ContentView.swift b/PostHogExample/ContentView.swift new file mode 100644 index 000000000..d2e258465 --- /dev/null +++ b/PostHogExample/ContentView.swift @@ -0,0 +1,193 @@ +// +// ContentView.swift +// PostHogExample +// +// Created by Ben White on 10.01.23. +// + +import AuthenticationServices +import PostHog +import SwiftUI + +class SignInViewModel: NSObject, ObservableObject, ASWebAuthenticationPresentationContextProviding { + // MARK: - ASWebAuthenticationPresentationContextProviding + + func presentationAnchor(for _: ASWebAuthenticationSession) -> ASPresentationAnchor { + ASPresentationAnchor() + } + + func triggerAuthentication() { + guard let authURL = URL(string: "https://example.com/auth") else { return } + let scheme = "exampleauth" + + // Initialize the session. + let session = ASWebAuthenticationSession(url: authURL, callbackURLScheme: scheme) { callbackURL, error in + if callbackURL != nil { + print("URL", callbackURL!.absoluteString) + } + if error != nil { + print("Error", error!.localizedDescription) + } + } + session.presentationContextProvider = self + session.prefersEphemeralWebBrowserSession = true + + session.start() + } +} + +class FeatureFlagsModel: ObservableObject { + @Published var boolValue: Bool? + @Published var stringValue: String? + @Published var payloadValue: [String: String]? + @Published var isReloading: Bool = false + + init() { + NotificationCenter.default.addObserver(self, selector: #selector(reloaded), name: PostHogSDK.didReceiveFeatureFlags, object: nil) + } + + @objc func reloaded() { + boolValue = PostHogSDK.shared.isFeatureEnabled("4535-funnel-bar-viz") + stringValue = PostHogSDK.shared.getFeatureFlag("multivariant") as? String + payloadValue = PostHogSDK.shared.getFeatureFlagPayload("multivariant") as? [String: String] + } + + func reload() { + isReloading = true + + PostHogSDK.shared.reloadFeatureFlags { + self.isReloading = false + } + } +} + +struct ContentView: View { + @State var counter: Int = 0 + @State private var name: String = "Tim" + @State private var showingSheet = false + @State private var showingRedactedSheet = false + @StateObject var api = Api() + + @StateObject var signInViewModel = SignInViewModel() + @StateObject var featureFlagsModel = FeatureFlagsModel() + + func incCounter() { + counter += 1 + } + + func triggerIdentify() { + PostHogSDK.shared.identify(name, userProperties: [ + "name": name, + ]) + } + + func triggerAuthentication() { + signInViewModel.triggerAuthentication() + } + + func triggerFlagReload() { + featureFlagsModel.reload() + } + + var body: some View { + NavigationStack { + List { + Section("General") { + NavigationLink { + ContentView() + } label: { + Text("Infinite navigation") + }.accessibilityIdentifier("ph-no-capture") + + Button("Show Sheet") { + showingSheet.toggle() + } + .sheet(isPresented: $showingSheet) { + ContentView() + } + Button("Show redacted view") { + showingRedactedSheet.toggle() + } + .sheet(isPresented: $showingRedactedSheet) { + RepresentedExampleUIView() + } + + Text("Sensitive text!!").accessibilityIdentifier("ph-no-capture") + Button(action: incCounter) { + Text(String(counter)) + }.accessibilityIdentifier("ph-no-capture-id").accessibilityLabel("ph-no-capture") + + TextField("Enter your name", text: $name) + Text("Hello, \(name)!") + Button(action: triggerAuthentication) { + Text("Trigger fake authentication!") + } + Button(action: triggerIdentify) { + Text("Trigger identify!") + } + } + + Section("Feature flags") { + HStack { + Text("Boolean:") + Spacer() + Text("\(featureFlagsModel.boolValue?.description ?? "unknown")") + .foregroundColor(.secondary) + } + HStack { + Text("String:") + Spacer() + Text("\(featureFlagsModel.stringValue ?? "unknown")") + .foregroundColor(.secondary) + } + + HStack { + Text("Payload:") + Spacer() + Text("\(featureFlagsModel.payloadValue?.description ?? "unknown")") + .foregroundColor(.secondary) + } + HStack { + Button(action: triggerFlagReload) { + Text("Reload flags") + } + Spacer() + if featureFlagsModel.isReloading { + ProgressView() + } + } + } + + Section("PostHog beers") { + if !api.beers.isEmpty { + ForEach(api.beers) { beer in + HStack(alignment: .center) { + Text(beer.name) + Spacer() + Text("First brewed") + Text(beer.first_brewed).foregroundColor(Color.gray) + } + } + } else { + HStack { + Text("Loading beers...") + Spacer() + ProgressView() + } + } + } + } + .navigationTitle("PostHog") + }.onAppear { + api.listBeers(completion: { beers in + api.beers = beers + }) + } + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} diff --git a/PostHogExample/PostHogExampleApp.swift b/PostHogExample/PostHogExampleApp.swift new file mode 100644 index 000000000..a9251a797 --- /dev/null +++ b/PostHogExample/PostHogExampleApp.swift @@ -0,0 +1,19 @@ +// +// PostHogExampleApp.swift +// PostHogExample +// +// Created by Ben White on 10.01.23. +// + +import SwiftUI + +@main +struct PostHogExampleApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/PostHogExample/Preview Content/Preview Assets.xcassets/Contents.json b/PostHogExample/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/PostHogExample/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PostHogExample/Views/UIViewExample.swift b/PostHogExample/Views/UIViewExample.swift new file mode 100644 index 000000000..69aac40d3 --- /dev/null +++ b/PostHogExample/Views/UIViewExample.swift @@ -0,0 +1,56 @@ +// +// UIViewExample.swift +// PostHogExample +// +// Created by Ben White on 17.03.23. +// + +import Foundation +import SwiftUI +import UIKit + +class ExampleUIView: UIView { + private var label: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.font = UIFont.preferredFont(forTextStyle: .title1) + label.text = "Sensitive information!" + label.textAlignment = .center + + return label + }() + + init() { + super.init(frame: .zero) + backgroundColor = .systemPink + accessibilityIdentifier = "ph-no-capture" + + addSubview(label) + NSLayoutConstraint.activate([ + label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16), + label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16), + label.topAnchor.constraint(equalTo: topAnchor, constant: 20), + label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20), + ]) + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +struct RepresentedExampleUIView: UIViewRepresentable { + typealias UIViewType = ExampleUIView + + func makeUIView(context _: Context) -> ExampleUIView { + let view = ExampleUIView() + + // Do some configurations here if needed. + return view + } + + func updateUIView(_: ExampleUIView, context _: Context) { + // Updates the state of the specified view controller with new information from SwiftUI. + } +} diff --git a/PostHogExampleWatchOS/PostHogExampleWatchOS Watch App/Assets.xcassets/AccentColor.colorset/Contents.json b/PostHogExampleWatchOS/PostHogExampleWatchOS Watch App/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000..eb8789700 --- /dev/null +++ b/PostHogExampleWatchOS/PostHogExampleWatchOS Watch App/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PostHogExampleWatchOS/PostHogExampleWatchOS Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json b/PostHogExampleWatchOS/PostHogExampleWatchOS Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..49c81cd8c --- /dev/null +++ b/PostHogExampleWatchOS/PostHogExampleWatchOS Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "watchos", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PostHogExampleWatchOS/PostHogExampleWatchOS Watch App/Assets.xcassets/Contents.json b/PostHogExampleWatchOS/PostHogExampleWatchOS Watch App/Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/PostHogExampleWatchOS/PostHogExampleWatchOS Watch App/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PostHogExampleWatchOS/PostHogExampleWatchOS Watch App/ContentView.swift b/PostHogExampleWatchOS/PostHogExampleWatchOS Watch App/ContentView.swift new file mode 100644 index 000000000..a55f00f06 --- /dev/null +++ b/PostHogExampleWatchOS/PostHogExampleWatchOS Watch App/ContentView.swift @@ -0,0 +1,24 @@ +// +// ContentView.swift +// PostHogExampleWatchOS Watch App +// +// Created by Manoel Aranda Neto on 02.11.23. +// + +import SwiftUI + +struct ContentView: View { + var body: some View { + VStack { + Image(systemName: "globe") + .imageScale(.large) + .foregroundStyle(.tint) + Text("Hello, world!") + } + .padding() + } +} + +#Preview { + ContentView() +} diff --git a/PostHogExampleWatchOS/PostHogExampleWatchOS Watch App/PostHogExampleWatchOSApp.swift b/PostHogExampleWatchOS/PostHogExampleWatchOS Watch App/PostHogExampleWatchOSApp.swift new file mode 100644 index 000000000..ba5067f2a --- /dev/null +++ b/PostHogExampleWatchOS/PostHogExampleWatchOS Watch App/PostHogExampleWatchOSApp.swift @@ -0,0 +1,29 @@ +// +// PostHogExampleWatchOSApp.swift +// PostHogExampleWatchOS Watch App +// +// Created by Manoel Aranda Neto on 02.11.23. +// + +import PostHog +import SwiftUI + +@main +struct PostHogExampleWatchOSApp: App { + init() { + // TODO: init on app delegate instead + let config = PostHogConfig( + apiKey: "_6SG-F7I1vCuZ-HdJL3VZQqjBlaSb1_20hDPwqMNnGI" + ) + + PostHogSDK.shared.setup(config) + PostHogSDK.shared.debug() + PostHogSDK.shared.capture("Event from WatchOS example!") + } + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/PostHogExampleWatchOS/PostHogExampleWatchOS Watch App/Preview Content/Preview Assets.xcassets/Contents.json b/PostHogExampleWatchOS/PostHogExampleWatchOS Watch App/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/PostHogExampleWatchOS/PostHogExampleWatchOS Watch App/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PostHogExampleWatchOS/PostHogExampleWatchOS.xcodeproj/project.pbxproj b/PostHogExampleWatchOS/PostHogExampleWatchOS.xcodeproj/project.pbxproj new file mode 100644 index 000000000..374a933dc --- /dev/null +++ b/PostHogExampleWatchOS/PostHogExampleWatchOS.xcodeproj/project.pbxproj @@ -0,0 +1,477 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 690FF15D2AF3CE8900A0B06B /* PostHogExampleWatchOS Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 690FF15C2AF3CE8900A0B06B /* PostHogExampleWatchOS Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 690FF1622AF3CE8900A0B06B /* PostHogExampleWatchOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 690FF1612AF3CE8900A0B06B /* PostHogExampleWatchOSApp.swift */; }; + 690FF1642AF3CE8900A0B06B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 690FF1632AF3CE8900A0B06B /* ContentView.swift */; }; + 690FF1662AF3CE8A00A0B06B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 690FF1652AF3CE8A00A0B06B /* Assets.xcassets */; }; + 690FF1692AF3CE8A00A0B06B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 690FF1682AF3CE8A00A0B06B /* Preview Assets.xcassets */; }; + 690FF1802AF3D12E00A0B06B /* PostHog.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 690FF17F2AF3D12E00A0B06B /* PostHog.framework */; }; + 690FF1812AF3D12E00A0B06B /* PostHog.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 690FF17F2AF3D12E00A0B06B /* PostHog.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 690FF15E2AF3CE8900A0B06B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 690FF1502AF3CE8900A0B06B /* Project object */; + proxyType = 1; + remoteGlobalIDString = 690FF15B2AF3CE8900A0B06B; + remoteInfo = "PostHogExampleWatchOS Watch App"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 690FF16F2AF3CE8A00A0B06B /* Embed Watch Content */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(CONTENTS_FOLDER_PATH)/Watch"; + dstSubfolderSpec = 16; + files = ( + 690FF15D2AF3CE8900A0B06B /* PostHogExampleWatchOS Watch App.app in Embed Watch Content */, + ); + name = "Embed Watch Content"; + runOnlyForDeploymentPostprocessing = 0; + }; + 690FF1822AF3D12E00A0B06B /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 690FF1812AF3D12E00A0B06B /* PostHog.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 690FF1562AF3CE8900A0B06B /* PostHogExampleWatchOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PostHogExampleWatchOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 690FF15C2AF3CE8900A0B06B /* PostHogExampleWatchOS Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "PostHogExampleWatchOS Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 690FF1612AF3CE8900A0B06B /* PostHogExampleWatchOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogExampleWatchOSApp.swift; sourceTree = ""; }; + 690FF1632AF3CE8900A0B06B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 690FF1652AF3CE8A00A0B06B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 690FF1682AF3CE8A00A0B06B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 690FF17F2AF3D12E00A0B06B /* PostHog.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = PostHog.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 690FF1592AF3CE8900A0B06B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 690FF1802AF3D12E00A0B06B /* PostHog.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 690FF14F2AF3CE8900A0B06B = { + isa = PBXGroup; + children = ( + 690FF1602AF3CE8900A0B06B /* PostHogExampleWatchOS Watch App */, + 690FF1572AF3CE8900A0B06B /* Products */, + 690FF17E2AF3D12E00A0B06B /* Frameworks */, + ); + sourceTree = ""; + }; + 690FF1572AF3CE8900A0B06B /* Products */ = { + isa = PBXGroup; + children = ( + 690FF1562AF3CE8900A0B06B /* PostHogExampleWatchOS.app */, + 690FF15C2AF3CE8900A0B06B /* PostHogExampleWatchOS Watch App.app */, + ); + name = Products; + sourceTree = ""; + }; + 690FF1602AF3CE8900A0B06B /* PostHogExampleWatchOS Watch App */ = { + isa = PBXGroup; + children = ( + 690FF1612AF3CE8900A0B06B /* PostHogExampleWatchOSApp.swift */, + 690FF1632AF3CE8900A0B06B /* ContentView.swift */, + 690FF1652AF3CE8A00A0B06B /* Assets.xcassets */, + 690FF1672AF3CE8A00A0B06B /* Preview Content */, + ); + path = "PostHogExampleWatchOS Watch App"; + sourceTree = ""; + }; + 690FF1672AF3CE8A00A0B06B /* Preview Content */ = { + isa = PBXGroup; + children = ( + 690FF1682AF3CE8A00A0B06B /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 690FF17E2AF3D12E00A0B06B /* Frameworks */ = { + isa = PBXGroup; + children = ( + 690FF17F2AF3D12E00A0B06B /* PostHog.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 690FF1552AF3CE8900A0B06B /* PostHogExampleWatchOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 690FF1702AF3CE8A00A0B06B /* Build configuration list for PBXNativeTarget "PostHogExampleWatchOS" */; + buildPhases = ( + 690FF1542AF3CE8900A0B06B /* Resources */, + 690FF16F2AF3CE8A00A0B06B /* Embed Watch Content */, + ); + buildRules = ( + ); + dependencies = ( + 690FF15F2AF3CE8900A0B06B /* PBXTargetDependency */, + ); + name = PostHogExampleWatchOS; + productName = PostHogExampleWatchOS; + productReference = 690FF1562AF3CE8900A0B06B /* PostHogExampleWatchOS.app */; + productType = "com.apple.product-type.application.watchapp2-container"; + }; + 690FF15B2AF3CE8900A0B06B /* PostHogExampleWatchOS Watch App */ = { + isa = PBXNativeTarget; + buildConfigurationList = 690FF16C2AF3CE8A00A0B06B /* Build configuration list for PBXNativeTarget "PostHogExampleWatchOS Watch App" */; + buildPhases = ( + 690FF1582AF3CE8900A0B06B /* Sources */, + 690FF1592AF3CE8900A0B06B /* Frameworks */, + 690FF15A2AF3CE8900A0B06B /* Resources */, + 690FF1822AF3D12E00A0B06B /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "PostHogExampleWatchOS Watch App"; + productName = "PostHogExampleWatchOS Watch App"; + productReference = 690FF15C2AF3CE8900A0B06B /* PostHogExampleWatchOS Watch App.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 690FF1502AF3CE8900A0B06B /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1500; + LastUpgradeCheck = 1500; + TargetAttributes = { + 690FF1552AF3CE8900A0B06B = { + CreatedOnToolsVersion = 15.0.1; + }; + 690FF15B2AF3CE8900A0B06B = { + CreatedOnToolsVersion = 15.0.1; + }; + }; + }; + buildConfigurationList = 690FF1532AF3CE8900A0B06B /* Build configuration list for PBXProject "PostHogExampleWatchOS" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 690FF14F2AF3CE8900A0B06B; + productRefGroup = 690FF1572AF3CE8900A0B06B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 690FF1552AF3CE8900A0B06B /* PostHogExampleWatchOS */, + 690FF15B2AF3CE8900A0B06B /* PostHogExampleWatchOS Watch App */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 690FF1542AF3CE8900A0B06B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 690FF15A2AF3CE8900A0B06B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 690FF1692AF3CE8A00A0B06B /* Preview Assets.xcassets in Resources */, + 690FF1662AF3CE8A00A0B06B /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 690FF1582AF3CE8900A0B06B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 690FF1642AF3CE8900A0B06B /* ContentView.swift in Sources */, + 690FF1622AF3CE8900A0B06B /* PostHogExampleWatchOSApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 690FF15F2AF3CE8900A0B06B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 690FF15B2AF3CE8900A0B06B /* PostHogExampleWatchOS Watch App */; + targetProxy = 690FF15E2AF3CE8900A0B06B /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 690FF16A2AF3CE8A00A0B06B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 690FF16B2AF3CE8A00A0B06B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = Release; + }; + 690FF16D2AF3CE8A00A0B06B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"PostHogExampleWatchOS Watch App/Preview Content\""; + DEVELOPMENT_TEAM = PNC2XCH2XP; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = PostHogExampleWatchOS; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_WKWatchOnly = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.posthog.PostHogExampleWatchOS.watchkitapp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 10.0; + }; + name = Debug; + }; + 690FF16E2AF3CE8A00A0B06B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"PostHogExampleWatchOS Watch App/Preview Content\""; + DEVELOPMENT_TEAM = PNC2XCH2XP; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = PostHogExampleWatchOS; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_WKWatchOnly = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.posthog.PostHogExampleWatchOS.watchkitapp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + VALIDATE_PRODUCT = YES; + WATCHOS_DEPLOYMENT_TARGET = 10.0; + }; + name = Release; + }; + 690FF1712AF3CE8A00A0B06B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = PNC2XCH2XP; + INFOPLIST_KEY_CFBundleDisplayName = PostHogExampleWatchOS; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.posthog.PostHogExampleWatchOS; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 690FF1722AF3CE8A00A0B06B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = PNC2XCH2XP; + INFOPLIST_KEY_CFBundleDisplayName = PostHogExampleWatchOS; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.posthog.PostHogExampleWatchOS; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_VERSION = 5.0; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 690FF1532AF3CE8900A0B06B /* Build configuration list for PBXProject "PostHogExampleWatchOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 690FF16A2AF3CE8A00A0B06B /* Debug */, + 690FF16B2AF3CE8A00A0B06B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 690FF16C2AF3CE8A00A0B06B /* Build configuration list for PBXNativeTarget "PostHogExampleWatchOS Watch App" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 690FF16D2AF3CE8A00A0B06B /* Debug */, + 690FF16E2AF3CE8A00A0B06B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 690FF1702AF3CE8A00A0B06B /* Build configuration list for PBXNativeTarget "PostHogExampleWatchOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 690FF1712AF3CE8A00A0B06B /* Debug */, + 690FF1722AF3CE8A00A0B06B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 690FF1502AF3CE8900A0B06B /* Project object */; +} diff --git a/PostHogExampleWithPods/Podfile b/PostHogExampleWithPods/Podfile new file mode 100644 index 000000000..cddbc24f2 --- /dev/null +++ b/PostHogExampleWithPods/Podfile @@ -0,0 +1,7 @@ +platform :ios, '16.0' + +use_frameworks! + +target 'PostHogExampleWithPods' do + pod 'PostHog', :path => '../' +end diff --git a/PostHogExampleWithPods/PostHogExampleWithPods.xcodeproj/project.pbxproj b/PostHogExampleWithPods/PostHogExampleWithPods.xcodeproj/project.pbxproj new file mode 100644 index 000000000..5af157bec --- /dev/null +++ b/PostHogExampleWithPods/PostHogExampleWithPods.xcodeproj/project.pbxproj @@ -0,0 +1,425 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 690FF0222AE7C5B900A0B06B /* PostHogExampleWithPodsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 690FF0212AE7C5B900A0B06B /* PostHogExampleWithPodsApp.swift */; }; + 690FF0242AE7C5B900A0B06B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 690FF0232AE7C5B900A0B06B /* ContentView.swift */; }; + 690FF0262AE7C5BA00A0B06B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 690FF0252AE7C5BA00A0B06B /* Assets.xcassets */; }; + 690FF0292AE7C5BA00A0B06B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 690FF0282AE7C5BA00A0B06B /* Preview Assets.xcassets */; }; + 690FF0362AE7C61300A0B06B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 690FF0352AE7C61300A0B06B /* AppDelegate.swift */; }; + C0A3DD993CB1FDADCC4FA4A3 /* Pods_PostHogExampleWithPods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 232850674C6F805AF056B5E2 /* Pods_PostHogExampleWithPods.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 232850674C6F805AF056B5E2 /* Pods_PostHogExampleWithPods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PostHogExampleWithPods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 690FF01E2AE7C5B900A0B06B /* PostHogExampleWithPods.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PostHogExampleWithPods.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 690FF0212AE7C5B900A0B06B /* PostHogExampleWithPodsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogExampleWithPodsApp.swift; sourceTree = ""; }; + 690FF0232AE7C5B900A0B06B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 690FF0252AE7C5BA00A0B06B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 690FF0282AE7C5BA00A0B06B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 690FF0352AE7C61300A0B06B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + A7382F16B47BF30B43A4A8D0 /* Pods-PostHogExampleWithPods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PostHogExampleWithPods.release.xcconfig"; path = "Target Support Files/Pods-PostHogExampleWithPods/Pods-PostHogExampleWithPods.release.xcconfig"; sourceTree = ""; }; + E20DD4077C3254D69E55F5E4 /* Pods-PostHogExampleWithPods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PostHogExampleWithPods.debug.xcconfig"; path = "Target Support Files/Pods-PostHogExampleWithPods/Pods-PostHogExampleWithPods.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 690FF01B2AE7C5B900A0B06B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C0A3DD993CB1FDADCC4FA4A3 /* Pods_PostHogExampleWithPods.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 40EAAE95CA89DD225C1A62CA /* Pods */ = { + isa = PBXGroup; + children = ( + E20DD4077C3254D69E55F5E4 /* Pods-PostHogExampleWithPods.debug.xcconfig */, + A7382F16B47BF30B43A4A8D0 /* Pods-PostHogExampleWithPods.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 51304F7636270ABD9ECC9BB4 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 232850674C6F805AF056B5E2 /* Pods_PostHogExampleWithPods.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 690FF0152AE7C5B900A0B06B = { + isa = PBXGroup; + children = ( + 690FF0202AE7C5B900A0B06B /* PostHogExampleWithPods */, + 690FF01F2AE7C5B900A0B06B /* Products */, + 40EAAE95CA89DD225C1A62CA /* Pods */, + 51304F7636270ABD9ECC9BB4 /* Frameworks */, + ); + sourceTree = ""; + }; + 690FF01F2AE7C5B900A0B06B /* Products */ = { + isa = PBXGroup; + children = ( + 690FF01E2AE7C5B900A0B06B /* PostHogExampleWithPods.app */, + ); + name = Products; + sourceTree = ""; + }; + 690FF0202AE7C5B900A0B06B /* PostHogExampleWithPods */ = { + isa = PBXGroup; + children = ( + 690FF0352AE7C61300A0B06B /* AppDelegate.swift */, + 690FF0212AE7C5B900A0B06B /* PostHogExampleWithPodsApp.swift */, + 690FF0232AE7C5B900A0B06B /* ContentView.swift */, + 690FF0252AE7C5BA00A0B06B /* Assets.xcassets */, + 690FF0272AE7C5BA00A0B06B /* Preview Content */, + ); + path = PostHogExampleWithPods; + sourceTree = ""; + }; + 690FF0272AE7C5BA00A0B06B /* Preview Content */ = { + isa = PBXGroup; + children = ( + 690FF0282AE7C5BA00A0B06B /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 690FF01D2AE7C5B900A0B06B /* PostHogExampleWithPods */ = { + isa = PBXNativeTarget; + buildConfigurationList = 690FF02C2AE7C5BA00A0B06B /* Build configuration list for PBXNativeTarget "PostHogExampleWithPods" */; + buildPhases = ( + E529A676C25D212940D59F59 /* [CP] Check Pods Manifest.lock */, + 690FF01A2AE7C5B900A0B06B /* Sources */, + 690FF01B2AE7C5B900A0B06B /* Frameworks */, + 690FF01C2AE7C5B900A0B06B /* Resources */, + 309133969B05247D96BFB94E /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = PostHogExampleWithPods; + productName = PostHogExampleWithPods; + productReference = 690FF01E2AE7C5B900A0B06B /* PostHogExampleWithPods.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 690FF0162AE7C5B900A0B06B /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1500; + LastUpgradeCheck = 1500; + TargetAttributes = { + 690FF01D2AE7C5B900A0B06B = { + CreatedOnToolsVersion = 15.0.1; + }; + }; + }; + buildConfigurationList = 690FF0192AE7C5B900A0B06B /* Build configuration list for PBXProject "PostHogExampleWithPods" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 690FF0152AE7C5B900A0B06B; + productRefGroup = 690FF01F2AE7C5B900A0B06B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 690FF01D2AE7C5B900A0B06B /* PostHogExampleWithPods */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 690FF01C2AE7C5B900A0B06B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 690FF0292AE7C5BA00A0B06B /* Preview Assets.xcassets in Resources */, + 690FF0262AE7C5BA00A0B06B /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 309133969B05247D96BFB94E /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PostHogExampleWithPods/Pods-PostHogExampleWithPods-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-PostHogExampleWithPods/Pods-PostHogExampleWithPods-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PostHogExampleWithPods/Pods-PostHogExampleWithPods-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + E529A676C25D212940D59F59 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-PostHogExampleWithPods-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 690FF01A2AE7C5B900A0B06B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 690FF0362AE7C61300A0B06B /* AppDelegate.swift in Sources */, + 690FF0242AE7C5B900A0B06B /* ContentView.swift in Sources */, + 690FF0222AE7C5B900A0B06B /* PostHogExampleWithPodsApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 690FF02A2AE7C5BA00A0B06B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 690FF02B2AE7C5BA00A0B06B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 690FF02D2AE7C5BA00A0B06B /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E20DD4077C3254D69E55F5E4 /* Pods-PostHogExampleWithPods.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"PostHogExampleWithPods/Preview Content\""; + DEVELOPMENT_TEAM = PNC2XCH2XP; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.posthog.PostHogExampleWithPods; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 690FF02E2AE7C5BA00A0B06B /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A7382F16B47BF30B43A4A8D0 /* Pods-PostHogExampleWithPods.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"PostHogExampleWithPods/Preview Content\""; + DEVELOPMENT_TEAM = PNC2XCH2XP; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.posthog.PostHogExampleWithPods; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 690FF0192AE7C5B900A0B06B /* Build configuration list for PBXProject "PostHogExampleWithPods" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 690FF02A2AE7C5BA00A0B06B /* Debug */, + 690FF02B2AE7C5BA00A0B06B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 690FF02C2AE7C5BA00A0B06B /* Build configuration list for PBXNativeTarget "PostHogExampleWithPods" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 690FF02D2AE7C5BA00A0B06B /* Debug */, + 690FF02E2AE7C5BA00A0B06B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 690FF0162AE7C5B900A0B06B /* Project object */; +} diff --git a/PostHogExampleWithPods/PostHogExampleWithPods/AppDelegate.swift b/PostHogExampleWithPods/PostHogExampleWithPods/AppDelegate.swift new file mode 100644 index 000000000..e8c2c0cb4 --- /dev/null +++ b/PostHogExampleWithPods/PostHogExampleWithPods/AppDelegate.swift @@ -0,0 +1,35 @@ +// +// AppDelegate.swift +// PostHogExampleWithPods +// +// Created by Manoel Aranda Neto on 24.10.23. +// + +import Foundation +import PostHog +import UIKit + +class AppDelegate: NSObject, UIApplicationDelegate { + func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { + let defaultCenter = NotificationCenter.default + + defaultCenter.addObserver(self, + selector: #selector(receiveFeatureFlags), + name: PostHogSDK.didReceiveFeatureFlags, + object: nil) + + let config = PostHogConfig( + apiKey: "_6SG-F7I1vCuZ-HdJL3VZQqjBlaSb1_20hDPwqMNnGI" + ) + + PostHogSDK.shared.setup(config) + PostHogSDK.shared.debug() + PostHogSDK.shared.capture("Event from CocoaPods example!") + + return true + } + + @objc func receiveFeatureFlags() { + print("receiveFeatureFlags") + } +} diff --git a/PostHogExampleWithPods/PostHogExampleWithPods/Assets.xcassets/AccentColor.colorset/Contents.json b/PostHogExampleWithPods/PostHogExampleWithPods/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000..eb8789700 --- /dev/null +++ b/PostHogExampleWithPods/PostHogExampleWithPods/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PostHogExampleWithPods/PostHogExampleWithPods/Assets.xcassets/AppIcon.appiconset/Contents.json b/PostHogExampleWithPods/PostHogExampleWithPods/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..13613e3ee --- /dev/null +++ b/PostHogExampleWithPods/PostHogExampleWithPods/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PostHogExampleWithPods/PostHogExampleWithPods/Assets.xcassets/Contents.json b/PostHogExampleWithPods/PostHogExampleWithPods/Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/PostHogExampleWithPods/PostHogExampleWithPods/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PostHogExampleWithPods/PostHogExampleWithPods/ContentView.swift b/PostHogExampleWithPods/PostHogExampleWithPods/ContentView.swift new file mode 100644 index 000000000..3ae9c1062 --- /dev/null +++ b/PostHogExampleWithPods/PostHogExampleWithPods/ContentView.swift @@ -0,0 +1,24 @@ +// +// ContentView.swift +// PostHogExampleWithPods +// +// Created by Manoel Aranda Neto on 24.10.23. +// + +import SwiftUI + +struct ContentView: View { + var body: some View { + VStack { + Image(systemName: "globe") + .imageScale(.large) + .foregroundStyle(.tint) + Text("Hello, world!") + } + .padding() + } +} + +#Preview { + ContentView() +} diff --git a/PostHogExampleWithPods/PostHogExampleWithPods/PostHogExampleWithPodsApp.swift b/PostHogExampleWithPods/PostHogExampleWithPods/PostHogExampleWithPodsApp.swift new file mode 100644 index 000000000..45d302730 --- /dev/null +++ b/PostHogExampleWithPods/PostHogExampleWithPods/PostHogExampleWithPodsApp.swift @@ -0,0 +1,19 @@ +// +// PostHogExampleWithPodsApp.swift +// PostHogExampleWithPods +// +// Created by Manoel Aranda Neto on 24.10.23. +// + +import SwiftUI + +@main +struct PostHogExampleWithPodsApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/PostHogExampleWithPods/PostHogExampleWithPods/Preview Content/Preview Assets.xcassets/Contents.json b/PostHogExampleWithPods/PostHogExampleWithPods/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/PostHogExampleWithPods/PostHogExampleWithPods/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PostHogExampleWithPods/README.md b/PostHogExampleWithPods/README.md new file mode 100644 index 000000000..7d599ec63 --- /dev/null +++ b/PostHogExampleWithPods/README.md @@ -0,0 +1,39 @@ +# Example with CocoaPods + +This is an example of how to use the PostHog iOS SDK with CocoaPods. + +## Installation + +1. Install CocoaPods if you haven't already: + + + +```bash +sudo gem install cocoapods +``` + +## Add a Podfile to the project + + + +```text +platform :ios, '16.0' + +use_frameworks! + +target '$your_project' do + pod 'PostHog', '~> 3.0.0' +end +``` + +Replace `$your_project` to your project name. + +## Run `pod install` + +```bash +pod install +``` + +## Open the project + +Always open the `*.xcworkspace` file, not the `*.xcodeproj` file. diff --git a/PostHogExampleWithSPM/.gitignore b/PostHogExampleWithSPM/.gitignore new file mode 100644 index 000000000..0023a5340 --- /dev/null +++ b/PostHogExampleWithSPM/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/PostHogExampleWithSPM/PostHogExampleWithSPM.xcodeproj/project.pbxproj b/PostHogExampleWithSPM/PostHogExampleWithSPM.xcodeproj/project.pbxproj new file mode 100644 index 000000000..4d16fe178 --- /dev/null +++ b/PostHogExampleWithSPM/PostHogExampleWithSPM.xcodeproj/project.pbxproj @@ -0,0 +1,377 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 60; + objects = { + +/* Begin PBXBuildFile section */ + 690FF0462AE7DB3600A0B06B /* PostHogExampleWithSPMApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 690FF0452AE7DB3600A0B06B /* PostHogExampleWithSPMApp.swift */; }; + 690FF0482AE7DB3600A0B06B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 690FF0472AE7DB3600A0B06B /* ContentView.swift */; }; + 690FF04A2AE7DB3700A0B06B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 690FF0492AE7DB3700A0B06B /* Assets.xcassets */; }; + 690FF04D2AE7DB3700A0B06B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 690FF04C2AE7DB3700A0B06B /* Preview Assets.xcassets */; }; + 690FF05A2AE7DD7500A0B06B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 690FF0592AE7DD7500A0B06B /* AppDelegate.swift */; }; + 690FF0612AE7E41800A0B06B /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = 690FF0602AE7E41800A0B06B /* PostHog */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 690FF0422AE7DB3600A0B06B /* PostHogExampleWithSPM.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PostHogExampleWithSPM.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 690FF0452AE7DB3600A0B06B /* PostHogExampleWithSPMApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogExampleWithSPMApp.swift; sourceTree = ""; }; + 690FF0472AE7DB3600A0B06B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 690FF0492AE7DB3700A0B06B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 690FF04C2AE7DB3700A0B06B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 690FF0592AE7DD7500A0B06B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 690FF03F2AE7DB3600A0B06B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 690FF0612AE7E41800A0B06B /* PostHog in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 690FF0392AE7DB3600A0B06B = { + isa = PBXGroup; + children = ( + 690FF0442AE7DB3600A0B06B /* PostHogExampleWithSPM */, + 690FF0432AE7DB3600A0B06B /* Products */, + ); + sourceTree = ""; + }; + 690FF0432AE7DB3600A0B06B /* Products */ = { + isa = PBXGroup; + children = ( + 690FF0422AE7DB3600A0B06B /* PostHogExampleWithSPM.app */, + ); + name = Products; + sourceTree = ""; + }; + 690FF0442AE7DB3600A0B06B /* PostHogExampleWithSPM */ = { + isa = PBXGroup; + children = ( + 690FF0592AE7DD7500A0B06B /* AppDelegate.swift */, + 690FF0452AE7DB3600A0B06B /* PostHogExampleWithSPMApp.swift */, + 690FF0472AE7DB3600A0B06B /* ContentView.swift */, + 690FF0492AE7DB3700A0B06B /* Assets.xcassets */, + 690FF04B2AE7DB3700A0B06B /* Preview Content */, + ); + path = PostHogExampleWithSPM; + sourceTree = ""; + }; + 690FF04B2AE7DB3700A0B06B /* Preview Content */ = { + isa = PBXGroup; + children = ( + 690FF04C2AE7DB3700A0B06B /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 690FF0412AE7DB3600A0B06B /* PostHogExampleWithSPM */ = { + isa = PBXNativeTarget; + buildConfigurationList = 690FF0502AE7DB3700A0B06B /* Build configuration list for PBXNativeTarget "PostHogExampleWithSPM" */; + buildPhases = ( + 690FF03E2AE7DB3600A0B06B /* Sources */, + 690FF03F2AE7DB3600A0B06B /* Frameworks */, + 690FF0402AE7DB3600A0B06B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = PostHogExampleWithSPM; + packageProductDependencies = ( + 690FF0602AE7E41800A0B06B /* PostHog */, + ); + productName = PostHogExampleWithSPM; + productReference = 690FF0422AE7DB3600A0B06B /* PostHogExampleWithSPM.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 690FF03A2AE7DB3600A0B06B /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1500; + LastUpgradeCheck = 1500; + TargetAttributes = { + 690FF0412AE7DB3600A0B06B = { + CreatedOnToolsVersion = 15.0.1; + }; + }; + }; + buildConfigurationList = 690FF03D2AE7DB3600A0B06B /* Build configuration list for PBXProject "PostHogExampleWithSPM" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 690FF0392AE7DB3600A0B06B; + packageReferences = ( + 690FF05B2AE7E03B00A0B06B /* XCLocalSwiftPackageReference ".." */, + ); + productRefGroup = 690FF0432AE7DB3600A0B06B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 690FF0412AE7DB3600A0B06B /* PostHogExampleWithSPM */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 690FF0402AE7DB3600A0B06B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 690FF04D2AE7DB3700A0B06B /* Preview Assets.xcassets in Resources */, + 690FF04A2AE7DB3700A0B06B /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 690FF03E2AE7DB3600A0B06B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 690FF05A2AE7DD7500A0B06B /* AppDelegate.swift in Sources */, + 690FF0482AE7DB3600A0B06B /* ContentView.swift in Sources */, + 690FF0462AE7DB3600A0B06B /* PostHogExampleWithSPMApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 690FF04E2AE7DB3700A0B06B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 690FF04F2AE7DB3700A0B06B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 690FF0512AE7DB3700A0B06B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"PostHogExampleWithSPM/Preview Content\""; + DEVELOPMENT_TEAM = PNC2XCH2XP; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.posthog.PostHogExampleWithSPM; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 690FF0522AE7DB3700A0B06B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"PostHogExampleWithSPM/Preview Content\""; + DEVELOPMENT_TEAM = PNC2XCH2XP; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.posthog.PostHogExampleWithSPM; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 690FF03D2AE7DB3600A0B06B /* Build configuration list for PBXProject "PostHogExampleWithSPM" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 690FF04E2AE7DB3700A0B06B /* Debug */, + 690FF04F2AE7DB3700A0B06B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 690FF0502AE7DB3700A0B06B /* Build configuration list for PBXNativeTarget "PostHogExampleWithSPM" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 690FF0512AE7DB3700A0B06B /* Debug */, + 690FF0522AE7DB3700A0B06B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 690FF05B2AE7E03B00A0B06B /* XCLocalSwiftPackageReference ".." */ = { + isa = XCLocalSwiftPackageReference; + relativePath = ..; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 690FF0602AE7E41800A0B06B /* PostHog */ = { + isa = XCSwiftPackageProductDependency; + productName = PostHog; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 690FF03A2AE7DB3600A0B06B /* Project object */; +} diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/PostHogExampleWithSPM/PostHogExampleWithSPM.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata rename to PostHogExampleWithSPM/PostHogExampleWithSPM.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/Examples/CocoapodsExample/CocoapodsExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/PostHogExampleWithSPM/PostHogExampleWithSPM.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from Examples/CocoapodsExample/CocoapodsExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to PostHogExampleWithSPM/PostHogExampleWithSPM.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/PostHogExampleWithSPM/PostHogExampleWithSPM/AppDelegate.swift b/PostHogExampleWithSPM/PostHogExampleWithSPM/AppDelegate.swift new file mode 100644 index 000000000..178f4d40f --- /dev/null +++ b/PostHogExampleWithSPM/PostHogExampleWithSPM/AppDelegate.swift @@ -0,0 +1,34 @@ +// +// AppDelegate.swift +// PostHogExampleWithPods +// +// Created by Manoel Aranda Neto on 24.10.23. +// +import Foundation +import PostHog +import UIKit + +class AppDelegate: NSObject, UIApplicationDelegate { + func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { + let defaultCenter = NotificationCenter.default + + defaultCenter.addObserver(self, + selector: #selector(receiveFeatureFlags), + name: PostHogSDK.didReceiveFeatureFlags, + object: nil) + + let config = PostHogConfig( + apiKey: "_6SG-F7I1vCuZ-HdJL3VZQqjBlaSb1_20hDPwqMNnGI" + ) + + PostHogSDK.shared.setup(config) + PostHogSDK.shared.debug() + PostHogSDK.shared.capture("Event from SPM example!") + + return true + } + + @objc func receiveFeatureFlags() { + print("receiveFeatureFlags") + } +} diff --git a/PostHogExampleWithSPM/PostHogExampleWithSPM/Assets.xcassets/AccentColor.colorset/Contents.json b/PostHogExampleWithSPM/PostHogExampleWithSPM/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000..eb8789700 --- /dev/null +++ b/PostHogExampleWithSPM/PostHogExampleWithSPM/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PostHogExampleWithSPM/PostHogExampleWithSPM/Assets.xcassets/AppIcon.appiconset/Contents.json b/PostHogExampleWithSPM/PostHogExampleWithSPM/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..13613e3ee --- /dev/null +++ b/PostHogExampleWithSPM/PostHogExampleWithSPM/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PostHogExampleWithSPM/PostHogExampleWithSPM/Assets.xcassets/Contents.json b/PostHogExampleWithSPM/PostHogExampleWithSPM/Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/PostHogExampleWithSPM/PostHogExampleWithSPM/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PostHogExampleWithSPM/PostHogExampleWithSPM/ContentView.swift b/PostHogExampleWithSPM/PostHogExampleWithSPM/ContentView.swift new file mode 100644 index 000000000..dd9fd5444 --- /dev/null +++ b/PostHogExampleWithSPM/PostHogExampleWithSPM/ContentView.swift @@ -0,0 +1,24 @@ +// +// ContentView.swift +// PostHogExampleWithSPM +// +// Created by Manoel Aranda Neto on 24.10.23. +// + +import SwiftUI + +struct ContentView: View { + var body: some View { + VStack { + Image(systemName: "globe") + .imageScale(.large) + .foregroundStyle(.tint) + Text("Hello, world!") + } + .padding() + } +} + +#Preview { + ContentView() +} diff --git a/PostHogExampleWithSPM/PostHogExampleWithSPM/PostHogExampleWithSPMApp.swift b/PostHogExampleWithSPM/PostHogExampleWithSPM/PostHogExampleWithSPMApp.swift new file mode 100644 index 000000000..74da7f57d --- /dev/null +++ b/PostHogExampleWithSPM/PostHogExampleWithSPM/PostHogExampleWithSPMApp.swift @@ -0,0 +1,19 @@ +// +// PostHogExampleWithSPMApp.swift +// PostHogExampleWithSPM +// +// Created by Manoel Aranda Neto on 24.10.23. +// + +import SwiftUI + +@main +struct PostHogExampleWithSPMApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/PostHogExampleWithSPM/PostHogExampleWithSPM/Preview Content/Preview Assets.xcassets/Contents.json b/PostHogExampleWithSPM/PostHogExampleWithSPM/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/PostHogExampleWithSPM/PostHogExampleWithSPM/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PostHogExampleWithSPM/README.md b/PostHogExampleWithSPM/README.md new file mode 100644 index 000000000..4de7095df --- /dev/null +++ b/PostHogExampleWithSPM/README.md @@ -0,0 +1,28 @@ +# Example with SPM + +This is an example of how to use the PostHog iOS SDK with SPM. + +## Installation + +1. Install Xcode if you haven't already: + +Follow steps from the [Xcode docs](https://developer.apple.com/xcode/resources/). + +## Add a SP dependency to the project via Xcode + +1. Click on the project in the Project Navigator. +2. Select the project in the Project and Targets list. +3. Select the General tab. +4. Scroll down to the Frameworks and Libraries section. +5. Click the + button. +6. Click Add Other... +7. Click Add Package Dependency +8. Enter the URL of the PostHog SDK repo or the local path to the repo. + +## Import the PostHog package + +```swift +import PostHog +``` + +And run the project. diff --git a/PostHogObjCExample/AppDelegate.h b/PostHogObjCExample/AppDelegate.h new file mode 100644 index 000000000..addf60dec --- /dev/null +++ b/PostHogObjCExample/AppDelegate.h @@ -0,0 +1,14 @@ +// +// AppDelegate.h +// PostHogObjCExample +// +// Created by Manoel Aranda Neto on 23.10.23. +// + +#import + +@interface AppDelegate : UIResponder + + +@end + diff --git a/PostHogObjCExample/AppDelegate.m b/PostHogObjCExample/AppDelegate.m new file mode 100644 index 000000000..ef15bf5d3 --- /dev/null +++ b/PostHogObjCExample/AppDelegate.m @@ -0,0 +1,114 @@ +// +// AppDelegate.m +// PostHogObjCExample +// +// Created by Manoel Aranda Neto on 23.10.23. +// + +#import "AppDelegate.h" +#import + +@interface AppDelegate () + +@end + +@implementation AppDelegate + + +- (void)receiveTestNotification { + NSLog(@"received"); +} + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Override point for customization after application launch. + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(receiveTestNotification) + name:PostHogSDK.didStartNotification + object:nil]; + + PostHogConfig *config = [[PostHogConfig alloc] apiKey:@"_6SG-F7I1vCuZ-HdJL3VZQqjBlaSb1_20hDPwqMNnGI"]; + config.preloadFeatureFlags = NO; + [[PostHogSDK shared] debug:YES]; + [[PostHogSDK shared] setup:config]; +// NSLog(@"getDistinctId: %@", [[PostHogSDK shared] getDistinctId]); +// NSLog(@"getAnonymousId: %@", [[PostHogSDK shared] getAnonymousId]); +// +// NSMutableDictionary *props = [NSMutableDictionary dictionary]; +// props[@"state"] = @"running"; +// +// NSMutableDictionary *userProps = [NSMutableDictionary dictionary]; +// userProps[@"userAge"] = @50; +// +// NSMutableDictionary *userPropsOnce = [NSMutableDictionary dictionary]; +// userPropsOnce[@"userAlive"] = @YES; +// +// NSMutableDictionary *groupProps = [NSMutableDictionary dictionary]; +// groupProps[@"groupName"] = @"theGroup"; +// +// NSMutableDictionary *registerProps = [NSMutableDictionary dictionary]; +// props[@"loggedIn"] = @YES; +// [[PostHogSDK shared] registerProperties:registerProps]; +// [[PostHogSDK shared] unregisterProperties:@"test2"]; +// +// [[PostHogSDK shared] identify:@"my_new_id"]; +// [[PostHogSDK shared] identifyWithDistinctId:@"my_new_id" userProperties:userProps]; +// [[PostHogSDK shared] identifyWithDistinctId:@"my_new_id" userProperties:userProps userPropertiesSetOnce:userPropsOnce]; +// +// +// [[PostHogSDK shared] optIn]; +// [[PostHogSDK shared] optOut]; +// NSLog(@"isOptOut: %d", [[PostHogSDK shared] isOptOut]); +// NSLog(@"isFeatureEnabled: %d", [[PostHogSDK shared] isFeatureEnabled:@"myFlag"]); +// NSLog(@"getFeatureFlag: %@", [[PostHogSDK shared] getFeatureFlag:@"myFlag"]); +// NSLog(@"getFeatureFlagPayload: %@", [[PostHogSDK shared] getFeatureFlagPayload:@"myFlag"]); +// +// [[PostHogSDK shared] reloadFeatureFlags]; +// [[PostHogSDK shared] reloadFeatureFlagsWithCallback:^(){ +// NSLog(@"called"); +// }]; +// +// [[PostHogSDK shared] capture:@"theEvent"]; +// [[PostHogSDK shared] captureWithEvent:@"theEvent" properties:props]; +// [[PostHogSDK shared] captureWithEvent:@"theEvent" properties:props userProperties:userProps]; +// [[PostHogSDK shared] captureWithEvent:@"theEvent" properties:props userProperties:userProps userPropertiesSetOnce:userPropsOnce]; +// [[PostHogSDK shared] captureWithEvent:@"theEvent" properties:props userProperties:userProps userPropertiesSetOnce:userPropsOnce groupProperties:groupProps]; +// +// [[PostHogSDK shared] groupWithType:@"theType" key:@"theKey"]; +// [[PostHogSDK shared] groupWithType:@"theType" key:@"theKey" groupProperties:groupProps]; +// +// [[PostHogSDK shared] alias:@"theAlias"]; +// +// [[PostHogSDK shared] screen:@"theScreen"]; +// [[PostHogSDK shared] screenWithTitle:@"theScreen" properties:props]; + +// [[PostHogSDK shared] flush]; +// [[PostHogSDK shared] reset]; +// [[PostHogSDK shared] close]; + +// PostHogSDK *postHog = [PostHogSDK with:config]; +// +// [postHog capture:@"theCapture"]; + + return YES; +} + + +#pragma mark - UISceneSession lifecycle + + +- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role]; +} + + +- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. +} + + +@end diff --git a/PostHogObjCExample/Assets.xcassets/AccentColor.colorset/Contents.json b/PostHogObjCExample/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000..eb8789700 --- /dev/null +++ b/PostHogObjCExample/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PostHogObjCExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/PostHogObjCExample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..13613e3ee --- /dev/null +++ b/PostHogObjCExample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PostHogObjCExample/Assets.xcassets/Contents.json b/PostHogObjCExample/Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/PostHogObjCExample/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Examples/CocoapodsExample/CocoapodsExample/Base.lproj/LaunchScreen.storyboard b/PostHogObjCExample/Base.lproj/LaunchScreen.storyboard similarity index 66% rename from Examples/CocoapodsExample/CocoapodsExample/Base.lproj/LaunchScreen.storyboard rename to PostHogObjCExample/Base.lproj/LaunchScreen.storyboard index fdf3f97d1..865e9329f 100644 --- a/Examples/CocoapodsExample/CocoapodsExample/Base.lproj/LaunchScreen.storyboard +++ b/PostHogObjCExample/Base.lproj/LaunchScreen.storyboard @@ -1,7 +1,8 @@ - + - + + @@ -9,14 +10,11 @@ - - - - - + + diff --git a/PostHogObjCExample/Base.lproj/Main.storyboard b/PostHogObjCExample/Base.lproj/Main.storyboard new file mode 100644 index 000000000..808a21ce7 --- /dev/null +++ b/PostHogObjCExample/Base.lproj/Main.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PostHogObjCExample/Info.plist b/PostHogObjCExample/Info.plist new file mode 100644 index 000000000..81ed29b76 --- /dev/null +++ b/PostHogObjCExample/Info.plist @@ -0,0 +1,25 @@ + + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + SceneDelegate + UISceneStoryboardFile + Main + + + + + + diff --git a/PostHogObjCExample/SceneDelegate.h b/PostHogObjCExample/SceneDelegate.h new file mode 100644 index 000000000..2da18f216 --- /dev/null +++ b/PostHogObjCExample/SceneDelegate.h @@ -0,0 +1,15 @@ +// +// SceneDelegate.h +// PostHogObjCExample +// +// Created by Manoel Aranda Neto on 23.10.23. +// + +#import + +@interface SceneDelegate : UIResponder + +@property (strong, nonatomic) UIWindow * window; + +@end + diff --git a/PostHogObjCExample/SceneDelegate.m b/PostHogObjCExample/SceneDelegate.m new file mode 100644 index 000000000..8564c7dca --- /dev/null +++ b/PostHogObjCExample/SceneDelegate.m @@ -0,0 +1,57 @@ +// +// SceneDelegate.m +// PostHogObjCExample +// +// Created by Manoel Aranda Neto on 23.10.23. +// + +#import "SceneDelegate.h" + +@interface SceneDelegate () + +@end + +@implementation SceneDelegate + + +- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). +} + + +- (void)sceneDidDisconnect:(UIScene *)scene { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). +} + + +- (void)sceneDidBecomeActive:(UIScene *)scene { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. +} + + +- (void)sceneWillResignActive:(UIScene *)scene { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). +} + + +- (void)sceneWillEnterForeground:(UIScene *)scene { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. +} + + +- (void)sceneDidEnterBackground:(UIScene *)scene { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. +} + + +@end diff --git a/PostHogObjCExample/ViewController.h b/PostHogObjCExample/ViewController.h new file mode 100644 index 000000000..0d5f9ffc3 --- /dev/null +++ b/PostHogObjCExample/ViewController.h @@ -0,0 +1,14 @@ +// +// ViewController.h +// PostHogObjCExample +// +// Created by Manoel Aranda Neto on 23.10.23. +// + +#import + +@interface ViewController : UIViewController + + +@end + diff --git a/PostHogObjCExample/ViewController.m b/PostHogObjCExample/ViewController.m new file mode 100644 index 000000000..2bdefee37 --- /dev/null +++ b/PostHogObjCExample/ViewController.m @@ -0,0 +1,22 @@ +// +// ViewController.m +// PostHogObjCExample +// +// Created by Manoel Aranda Neto on 23.10.23. +// + +#import "ViewController.h" + +@interface ViewController () + +@end + +@implementation ViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view. +} + + +@end diff --git a/PostHogObjCExample/main.m b/PostHogObjCExample/main.m new file mode 100644 index 000000000..0db7ddacb --- /dev/null +++ b/PostHogObjCExample/main.m @@ -0,0 +1,18 @@ +// +// main.m +// PostHogObjCExample +// +// Created by Manoel Aranda Neto on 23.10.23. +// + +#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + NSString * appDelegateClassName; + @autoreleasepool { + // Setup code that might create autoreleased objects goes here. + appDelegateClassName = NSStringFromClass([AppDelegate class]); + } + return UIApplicationMain(argc, argv, nil, appDelegateClassName); +} diff --git a/PostHogTests/CapturingTests.swift b/PostHogTests/CapturingTests.swift deleted file mode 100644 index 8fa3c82e3..000000000 --- a/PostHogTests/CapturingTests.swift +++ /dev/null @@ -1,97 +0,0 @@ -import Quick -import Nimble -import PostHog - -class CapturingTests: QuickSpec { - override func spec() { - var passthrough: PHGPassthroughMiddleware! - var posthog: PHGPostHog! - - beforeEach { - let config = PHGPostHogConfiguration(apiKey: "foobar") - passthrough = PHGPassthroughMiddleware() - config.middlewares = [ - passthrough, - ] - posthog = PHGPostHog(configuration: config) - } - - afterEach { - posthog.reset() - } - - it("handles identify:") { - posthog.identify("testDistinctId1", properties: [ - "firstName": "Peter" - ]) - expect(passthrough.lastContext?.eventType) == PHGEventType.identify - let identify = passthrough.lastContext?.payload as? PHGIdentifyPayload - expect(identify?.distinctId) == "testDistinctId1" - expect(identify?.anonymousId).toNot(beNil()) - expect(identify?.properties?["firstName"] as? String) == "Peter" - } - - it("handles identify with custom anonymousId:") { - posthog.identify("testDistinctId1", properties: [ - "firstName": "Peter" - ], options: [ - "$anon_distinct_id": "a_custom_anonymous_id" - ]) - expect(passthrough.lastContext?.eventType) == PHGEventType.identify - let identify = passthrough.lastContext?.payload as? PHGIdentifyPayload - expect(identify?.distinctId) == "testDistinctId1" - expect(identify?.anonymousId) == "a_custom_anonymous_id" - expect(identify?.properties?["firstName"] as? String) == "Peter" - } - - it("handles capture:") { - posthog.capture("User Signup", properties: [ - "method": "SSO" - ]) - expect(passthrough.lastContext?.eventType) == PHGEventType.capture - let payload = passthrough.lastContext?.payload as? PHGCapturePayload - expect(payload?.event) == "User Signup" - expect(payload?.properties?["method"] as? String) == "SSO" - } - - it("handles alias:") { - posthog.alias("persistentDistinctId") - expect(passthrough.lastContext?.eventType) == PHGEventType.alias - let payload = passthrough.lastContext?.payload as? PHGAliasPayload - expect(payload?.alias) == "persistentDistinctId" - } - - it("handles screen:") { - posthog.screen("Home", properties: [ - "referrer": "Google" - ]) - expect(passthrough.lastContext?.eventType) == PHGEventType.screen - let screen = passthrough.lastContext?.payload as? PHGScreenPayload - expect(screen?.name) == "Home" - expect(screen?.properties?["referrer"] as? String) == "Google" - } - - it("handles group:") { - posthog.group( "some-type", groupKey: "some-key", properties: [ - "name": "some-company-name" - ]) - let firstContext = passthrough.allContexts[1] - - expect(firstContext.eventType) == PHGEventType.group - let payload = firstContext.payload as? PHGGroupPayload - expect(payload?.groupType) == "some-type" - expect(payload?.properties?["name"] as? String) == "some-company-name" - - } - - it("handles null values") { - posthog.capture("null test", properties: [ - "nullTest": NSNull() - ]) - let payload = passthrough.lastContext?.payload as? PHGCapturePayload - let isNull = (payload?.properties?["nullTest"] is NSNull) - expect(isNull) == true - } - } - -} diff --git a/PostHogTests/ContextTest.swift b/PostHogTests/ContextTest.swift deleted file mode 100644 index e6ba806b8..000000000 --- a/PostHogTests/ContextTest.swift +++ /dev/null @@ -1,81 +0,0 @@ -import Quick -import Nimble -import PostHog - -class ContextTests: QuickSpec { - override func spec() { - - var posthog: PHGPostHog! - - beforeEach { - let config = PHGPostHogConfiguration(apiKey: "foobar") - posthog = PHGPostHog(configuration: config) - } - - it("throws when used incorrectly") { - var context: PHGContext? - var exception: Error? - - do { - try ObjC.catchException { - context = PHGContext() - } - } - catch { - exception = error - } - - expect(context).to(beNil()) - expect(exception).toNot(beNil()) - } - - - it("initialized correctly") { - let context = PHGContext(postHog: posthog) - expect(context._posthog) == posthog - expect(context.eventType) == PHGEventType.undefined - } - - it("accepts modifications") { - let context = PHGContext(postHog: posthog) - - let newContext = context.modify { context in - context.distinctId = "sloth" - context.eventType = .capture; - } - expect(newContext.distinctId) == "sloth" - expect(newContext.eventType) == PHGEventType.capture; - - } - - it("modifies copy in debug mode to catch bugs") { - let context = PHGContext(postHog: posthog).modify { context in - context.debug = true - } - expect(context.debug) == true - - let newContext = context.modify { context in - context.distinctId = "123" - } - expect(context) !== newContext - expect(newContext.distinctId) == "123" - expect(context.distinctId).to(beNil()) - } - - it("modifies self in non-debug mode to optimize perf.") { - let context = PHGContext(postHog: posthog).modify { context in - context.debug = false - } - expect(context.debug) == false - - let newContext = context.modify { context in - context.distinctId = "123" - } - expect(context) === newContext - expect(newContext.distinctId) == "123" - expect(context.distinctId) == "123" - } - - } - -} diff --git a/PostHogTests/CryptoTest.swift b/PostHogTests/CryptoTest.swift deleted file mode 100644 index b1e4ca6d1..000000000 --- a/PostHogTests/CryptoTest.swift +++ /dev/null @@ -1,54 +0,0 @@ -import Quick -import Nimble -import PostHog - -class CryptoTest : QuickSpec { - override func spec() { - var crypto : PHGAES256Crypto! - beforeEach { - crypto = PHGAES256Crypto(password: "slothysloth") - } - - it("encrypts and decrypts data") { - let strIn = "posthog" - let dataIn = strIn.data(using: String.Encoding.utf8)! - let encryptedData = crypto.encrypt(dataIn) - expect(encryptedData).toNot(beNil()) - - let dataOut = crypto.decrypt(encryptedData!) - expect(dataOut) == dataIn - - let strOut = String(data: dataOut!, encoding: String.Encoding.utf8) - expect(strOut) == "posthog" - } - - it("fails for incorrect password") { - let strIn = "posthog" - let dataIn = strIn.data(using: String.Encoding.utf8)! - let encryptedData = crypto.encrypt(dataIn) - expect(encryptedData).toNot(beNil()) - - let crypto2 = PHGAES256Crypto(password: "wolf", salt: crypto.salt, iv: crypto.iv) - let dataOut = crypto2.decrypt(encryptedData!) - expect(dataOut) != dataIn - let strOut = String(data: dataOut!, encoding: String.Encoding.utf8) - // no built in way to check password correctness - // http://stackoverflow.com/questions/27712173/determine-if-key-is-incorrect-with-cccrypt-kccoptionpkcs7padding-objective-c - expect(strOut ?? "") != strIn - } - - it("fails for incorrect iv and sault") { - let strIn = "posthog" - let dataIn = strIn.data(using: String.Encoding.utf8)! - let encryptedData = crypto.encrypt(dataIn) - expect(encryptedData).toNot(beNil()) - - let crypto2 = PHGAES256Crypto(password: crypto.password) - let dataOut = crypto2.decrypt(encryptedData!) - expect(dataOut) != dataIn - - let strOut = String(data: dataOut!, encoding: String.Encoding.utf8) - expect(strOut ?? "") != strIn - } - } -} diff --git a/PostHogTests/EndToEndTests.swift b/PostHogTests/EndToEndTests.swift deleted file mode 100644 index 36114cd22..000000000 --- a/PostHogTests/EndToEndTests.swift +++ /dev/null @@ -1,32 +0,0 @@ -import Quick -import Nimble -import PostHog -import Alamofire -import Alamofire_Synchronous - -// We try to make a real request to app.posthog.com and get a 200 response -class PostHogE2ETests: QuickSpec { - override func spec() { - var posthog: PHGPostHog! - - beforeEach { - let config = PHGPostHogConfiguration(apiKey: "foobar") - config.flushAt = 1 - - PHGPostHog.setup(with: config) - - posthog = PHGPostHog.shared() - } - - afterEach { - posthog.reset() - } - - it("capture") { - let uuid = UUID().uuidString - self.expectation(forNotification: NSNotification.Name("PostHogRequestDidSucceed"), object: nil, handler: nil) - posthog.capture("E2E Test", properties: ["id": uuid]) - self.waitForExpectations(timeout: 30) - } - } -} diff --git a/PostHogTests/FeatureFlagTests.swift b/PostHogTests/FeatureFlagTests.swift deleted file mode 100644 index b2a15f714..000000000 --- a/PostHogTests/FeatureFlagTests.swift +++ /dev/null @@ -1,264 +0,0 @@ -import Quick -import Nimble -import Nocilla -import PostHog - -class FeatureFlagTests: QuickSpec { - override func spec() { - var passthrough: PHGPassthroughMiddleware! - var posthog: PHGPostHog! - - beforeEach { - LSNocilla.sharedInstance().start() - let config = PHGPostHogConfiguration(apiKey: "foobar", host: "https://app.posthog.test") - passthrough = PHGPassthroughMiddleware() - config.middlewares = [ - passthrough, - ] - posthog = PHGPostHog(configuration: config) - } - - afterEach { - posthog.reset() - LSNocilla.sharedInstance().clearStubs() - LSNocilla.sharedInstance().stop() - } - - it("checks flag is enabled") { - _ = stubRequest("POST", "https://app.posthog.test/decide/?v=3" as LSMatcheable) - .andReturn(200)? - .withBody("{\"featureFlags\":{\"some-flag\": true}}" as LSHTTPBody); - posthog.reloadFeatureFlags() - // Hacky: Need to buffer for async request to happen without stub being cleaned up - sleep(1) - let isEnabled = posthog.isFeatureEnabled("some-flag") - expect(isEnabled).to(beTrue()) - } - - it("checks flag is disabled") { - _ = stubRequest("POST", "https://app.posthog.test/decide/?v=3" as LSMatcheable) - .andReturn(200)? - .withBody("{\"featureFlags\":{\"some-flag\": false}}" as LSHTTPBody); - posthog.reloadFeatureFlags() - // Hacky: Need to buffer for async request to happen without stub being cleaned up - sleep(1) - let isEnabled = posthog.isFeatureEnabled("some-flag") - expect(isEnabled).to(beFalse()) - } - - it("checks multivariate flag is enabled - integer") { - _ = stubRequest("POST", "https://app.posthog.test/decide/?v=3" as LSMatcheable) - .andReturn(200)? - .withBody("{\"featureFlags\":{\"some-flag\":1}}" as LSHTTPBody); - posthog.reloadFeatureFlags() - // Hacky: Need to buffer for async request to happen without stub being cleaned up - sleep(1) - let flagValue = posthog.getFeatureFlag("some-flag") - expect(flagValue).to(be(1)) - } - - it("checks multivariate flag is enabled - string") { - _ = stubRequest("POST", "https://app.posthog.test/decide/?v=3" as LSMatcheable) - .andReturn(200)? - .withBody("{\"featureFlags\":{\"some-flag\":\"variant-1\"}}" as LSHTTPBody); - posthog.reloadFeatureFlags() - // Hacky: Need to buffer for async request to happen without stub being cleaned up - sleep(1) - let flagValue = posthog.getFeatureFlag("some-flag") - - expect(flagValue).to(be("variant-1")) - } - - it("retrieves feature flag payload - nil") { - _ = stubRequest("POST", "https://app.posthog.test/decide/?v=3" as LSMatcheable) - .andReturn(200)? - .withBody("{\"featureFlags\":{\"some-flag\":\"variant-1\"}}" as LSHTTPBody); - posthog.reloadFeatureFlags() - // Hacky: Need to buffer for async request to happen without stub being cleaned up - sleep(1) - let flagPayload = posthog.getFeatureFlagStringPayload("some-flag", defaultValue: "default-payload") - - expect(flagPayload).to(equal("default-payload")) - } - - it("retrieves feature flag payload - string") { - _ = stubRequest("POST", "https://app.posthog.test/decide/?v=3" as LSMatcheable) - .andReturn(200)? - .withBody("{\"featureFlags\":{\"some-flag\":\"variant-1\"}, \"featureFlagPayloads\":{\"some-flag\":\"variant-payload\"}}" as LSHTTPBody); - posthog.reloadFeatureFlags() - // Hacky: Need to buffer for async request to happen without stub being cleaned up - sleep(1) - let flagPayload = posthog.getFeatureFlagStringPayload("some-flag", defaultValue: "default-payload") - - expect(flagPayload).to(equal("variant-payload")) - } - - it("retrieves feature flag payload error - string") { - _ = stubRequest("POST", "https://app.posthog.test/decide/?v=3" as LSMatcheable) - .andReturn(200)? - .withBody("{\"featureFlags\":{\"some-flag\":\"variant-1\"}, \"featureFlagPayloads\":{\"some-flag\": 2.0}}" as LSHTTPBody); - posthog.reloadFeatureFlags() - // Hacky: Need to buffer for async request to happen without stub being cleaned up - sleep(1) - let flagPayload = posthog.getFeatureFlagStringPayload("some-flag", defaultValue: "default-payload") - - expect(flagPayload).to(equal("default-payload")) - } - - it("retrieves feature flag payload - integer") { - _ = stubRequest("POST", "https://app.posthog.test/decide/?v=3" as LSMatcheable) - .andReturn(200)? - .withBody("{\"featureFlags\":{\"some-flag\":\"variant-1\"}, \"featureFlagPayloads\":{\"some-flag\": 2000}}" as LSHTTPBody); - posthog.reloadFeatureFlags() - // Hacky: Need to buffer for async request to happen without stub being cleaned up - sleep(1) - let flagPayload = posthog.getFeatureFlagIntegerPayload("some-flag", defaultValue: 3) - - expect(flagPayload).to(be(2000)) - } - - it("retrieves feature flag payload error - integer") { - _ = stubRequest("POST", "https://app.posthog.test/decide/?v=3" as LSMatcheable) - .andReturn(200)? - .withBody("{\"featureFlags\":{\"some-flag\":\"variant-1\"}, \"featureFlagPayloads\":{\"some-flag\": \"string-value\"}}" as LSHTTPBody); - posthog.reloadFeatureFlags() - // Hacky: Need to buffer for async request to happen without stub being cleaned up - sleep(1) - let flagPayload = posthog.getFeatureFlagIntegerPayload("some-flag", defaultValue: 3) - - expect(flagPayload).to(be(3)) - } - - it("retrieves feature flag payload - double") { - _ = stubRequest("POST", "https://app.posthog.test/decide/?v=3" as LSMatcheable) - .andReturn(200)? - .withBody("{\"featureFlags\":{\"some-flag\":\"variant-1\"}, \"featureFlagPayloads\":{\"some-flag\": 2.000}}" as LSHTTPBody); - posthog.reloadFeatureFlags() - // Hacky: Need to buffer for async request to happen without stub being cleaned up - sleep(1) - let flagPayload = posthog.getFeatureFlagDoublePayload("some-flag", defaultValue: 3.0) - - expect(flagPayload).to(be(2.000)) - } - - it("retrieves feature flag payload error - double") { - _ = stubRequest("POST", "https://app.posthog.test/decide/?v=3" as LSMatcheable) - .andReturn(200)? - .withBody("{\"featureFlags\":{\"some-flag\":\"variant-1\"}, \"featureFlagPayloads\":{\"some-flag\": {\"some-flag\":\"variant-1\"}}}" as LSHTTPBody); - posthog.reloadFeatureFlags() - // Hacky: Need to buffer for async request to happen without stub being cleaned up - sleep(1) - let flagPayload = posthog.getFeatureFlagDoublePayload("some-flag", defaultValue: 3.0) - - expect(flagPayload).to(be(3.0)) - } - - it("retrieves feature flag payload - json") { - _ = stubRequest("POST", "https://app.posthog.test/decide/?v=3" as LSMatcheable) - .andReturn(200)? - .withBody("{\"featureFlags\":{\"some-flag\":\"variant-1\"}, \"featureFlagPayloads\":{\"some-flag\": {\"some-flag\":\"variant-1\"}}}" as LSHTTPBody); - posthog.reloadFeatureFlags() - // Hacky: Need to buffer for async request to happen without stub being cleaned up - sleep(1) - let flagPayload = posthog.getFeatureFlagDictionaryPayload("some-flag", defaultValue: ["some-flag": "default-payload"]) - expect((flagPayload as! [String: String]) == (["some-flag": "variant-1"])).to(be(true)) - } - - it("retrieves feature flag payload error - json") { - _ = stubRequest("POST", "https://app.posthog.test/decide/?v=3" as LSMatcheable) - .andReturn(200)? - .withBody("{\"featureFlags\":{\"some-flag\":\"variant-1\"}, \"featureFlagPayloads\":{\"some-flag\": 2.00}}" as LSHTTPBody); - posthog.reloadFeatureFlags() - // Hacky: Need to buffer for async request to happen without stub being cleaned up - sleep(1) - let flagPayload = posthog.getFeatureFlagDictionaryPayload("some-flag", defaultValue: ["some-flag": "default-payload"]) - - expect((flagPayload as! [String: String]) == (["some-flag": "default-payload"])).to(be(true)) - } - - it("retrieves feature flag payload - array") { - _ = stubRequest("POST", "https://app.posthog.test/decide/?v=3" as LSMatcheable) - .andReturn(200)? - .withBody("{\"featureFlags\":{\"some-flag\":\"variant-1\"}, \"featureFlagPayloads\":{\"some-flag\": [\"some-flag\",\"variant-1\"]}}" as LSHTTPBody); - posthog.reloadFeatureFlags() - // Hacky: Need to buffer for async request to happen without stub being cleaned up - sleep(1) - let flagPayload = posthog.getFeatureFlagArrayPayload("some-flag", defaultValue: ["some-flag", "default-payload"]) - expect((flagPayload as! [String]) == (["some-flag", "variant-1"])).to(be(true)) - } - - it("retrieves feature flag payload error - array") { - _ = stubRequest("POST", "https://app.posthog.test/decide/?v=3" as LSMatcheable) - .andReturn(200)? - .withBody("{\"featureFlags\":{\"some-flag\":\"variant-1\"}, \"featureFlagPayloads\":{\"some-flag\": 2.00}}" as LSHTTPBody); - posthog.reloadFeatureFlags() - // Hacky: Need to buffer for async request to happen without stub being cleaned up - sleep(1) - let flagPayload = posthog.getFeatureFlagArrayPayload("some-flag", defaultValue: ["some-flag", "default-payload"]) - expect((flagPayload as! [String]) == (["some-flag", "default-payload"])).to(be(true)) - } - - it("retrieves feature flag payload - number") { - _ = stubRequest("POST", "https://app.posthog.test/decide/?v=3" as LSMatcheable) - .andReturn(200)? - .withBody("{\"featureFlags\":{\"some-flag\":\"variant-1\"}}" as LSHTTPBody); - posthog.reloadFeatureFlags() - // Hacky: Need to buffer for async request to happen without stub being cleaned up - sleep(1) - let flagValue = posthog.getFeatureFlag("some-flag") - expect(flagValue).to(be("variant-1")) - } - - it("retrieves feature flag payload - object") { - _ = stubRequest("POST", "https://app.posthog.test/decide/?v=3" as LSMatcheable) - .andReturn(200)? - .withBody("{\"featureFlags\":{\"some-flag\":\"variant-1\"}}" as LSHTTPBody); - posthog.reloadFeatureFlags() - // Hacky: Need to buffer for async request to happen without stub being cleaned up - sleep(1) - let flagValue = posthog.getFeatureFlag("some-flag") - expect(flagValue).to(be("variant-1")) - } - - it("bad request does not override current flags") { - _ = stubRequest("POST", "https://app.posthog.test/decide/?v=3" as LSMatcheable) - .andReturn(200)? - .withBody("{\"featureFlags\":{\"some-flag\": true}}" as LSHTTPBody); - posthog.reloadFeatureFlags() - // Hacky: Need to buffer for async request to happen without stub being cleaned up - sleep(1) - let isEnabled = posthog.isFeatureEnabled("some-flag") - expect(isEnabled).to(beTrue()) - - _ = stubRequest("POST", "https://app.posthog.test/decide/?v=3" as LSMatcheable) - .andReturn(400); - posthog.reloadFeatureFlags() - // Hacky: Need to buffer for async request to happen without stub being cleaned up - sleep(1) - let secondIsEnabled = posthog.isFeatureEnabled("some-flag") - expect(secondIsEnabled).to(beTrue()) - } - - it("Won't send $feature_flag_called if option is set to false") { - _ = stubRequest("POST", "https://app.posthog.test/decide/?v=3" as LSMatcheable) - .andReturn(200)? - .withBody("{\"featureFlags\":{\"some-flag\": true}}" as LSHTTPBody); - posthog.reloadFeatureFlags() - // Hacky: Need to buffer for async request to happen without stub being cleaned up - sleep(1) - let isEnabled = posthog.isFeatureEnabled("some-flag", options: [ - "send_event": false - ]) - var allContextTypes = passthrough.allContexts.map { $0.eventType } - expect(allContextTypes).notTo(contain(PHGEventType.capture)) - - posthog.isFeatureEnabled("some-flag", options: [ - "send_event": true - ]) - allContextTypes = passthrough.allContexts.map { $0.eventType } - expect(allContextTypes).to(contain(PHGEventType.capture)) - } - - } - -} diff --git a/PostHogTests/FileStorageTest.swift b/PostHogTests/FileStorageTest.swift deleted file mode 100644 index 7c82cb02e..000000000 --- a/PostHogTests/FileStorageTest.swift +++ /dev/null @@ -1,126 +0,0 @@ -import Quick -import Nimble -import PostHog - -class FileStorageTest : QuickSpec { - override func spec() { - var storage : PHGFileStorage! - beforeEach { - let url = PHGFileStorage.applicationSupportDirectoryURL() - expect(url).toNot(beNil()) - expect(url?.pathComponents[url!.pathComponents.count - 2]) == "Application Support" - expect(url?.lastPathComponent) == Bundle.main.bundleIdentifier - storage = PHGFileStorage(folder: url!, crypto: nil) - } - - it("Creates caches directory") { - let url = PHGFileStorage.cachesDirectoryURL() - expect(url).toNot(beNil()) - expect(url?.pathComponents[url!.pathComponents.count - 2]) == "Caches" - expect(url?.lastPathComponent) == Bundle.main.bundleIdentifier - } - - it("creates folder if none exists") { - let tempDir = NSURL(fileURLWithPath: NSTemporaryDirectory()) - let url = tempDir.appendingPathComponent(NSUUID().uuidString) - - expect(try? url?.checkResourceIsReachable()).to(beNil()) - _ = PHGFileStorage(folder: url!, crypto: nil) - - var isDir: ObjCBool = false - let exists = FileManager.default.fileExists(atPath: url!.path, isDirectory: &isDir) - - expect(exists) == true - expect(isDir.boolValue) == true - } - - it("persists and loads data") { - let dataIn = "posthog".data(using: String.Encoding.utf8)! - storage.setData(dataIn, forKey: "mydata") - - let dataOut = storage.data(forKey: "mydata") - expect(dataOut) == dataIn - - let strOut = String(data: dataOut!, encoding: String.Encoding.utf8) - expect(strOut) == "posthog" - } - - it("persists and loads string") { - let str = "san francisco" - storage.setString(str, forKey: "city") - expect(storage.string(forKey: "city")) == str - - storage.removeKey("city") - expect(storage.string(forKey: "city")).to(beNil()) - } - - it("persists and loads array") { - let array = [ - "san francisco", - "new york", - "tallinn", - ] - storage.setArray(array, forKey: "cities") - expect(storage.array(forKey: "cities") as? Array) == array - - storage.removeKey("cities") - expect(storage.array(forKey: "cities")).to(beNil()) - } - - it("persists and loads dictionary") { - let dict = [ - "san francisco": "tech", - "new york": "finance", - "paris": "fashion", - ] - storage.setDictionary(dict, forKey: "cityMap") - expect(storage.dictionary(forKey: "cityMap") as? Dictionary) == dict - - storage.removeKey("cityMap") - expect(storage.dictionary(forKey: "cityMap")).to(beNil()) - } - - it("saves file to disk and removes from disk") { - let key = "input.txt" - let url = storage.url(forKey: key) - expect(try? url.checkResourceIsReachable()).to(beNil()) - storage.setString("sloth", forKey: key) - expect(try! url.checkResourceIsReachable()) == true - storage.removeKey(key) - expect(try? url.checkResourceIsReachable()).to(beNil()) - } - - it("should be binary compatible with old SDKs") { - let key = "properties.plist" - let dictIn = [ - "san francisco": "tech", - "new york": "finance", - "paris": "fashion", - ] - - (dictIn as NSDictionary).write(to: storage.url(forKey: key), atomically: true) - let dictOut = storage.dictionary(forKey: key) - expect(dictOut as? [String: String]) == dictIn - } - - it("should work with crypto") { - let url = PHGFileStorage.applicationSupportDirectoryURL() - let crypto = PHGAES256Crypto(password: "thetrees") - let s = PHGFileStorage(folder: url!, crypto: crypto) - let dict = [ - "san francisco": "tech", - "new york": "finance", - "paris": "fashion", - ] - s.setDictionary(dict, forKey: "cityMap") - expect(s.dictionary(forKey: "cityMap") as? Dictionary) == dict - - s.removeKey("cityMap") - expect(s.dictionary(forKey: "cityMap")).to(beNil()) - } - - afterEach { - storage.resetAll() - } - } -} diff --git a/PostHogTests/HTTPClientTest.swift b/PostHogTests/HTTPClientTest.swift deleted file mode 100644 index 191095b46..000000000 --- a/PostHogTests/HTTPClientTest.swift +++ /dev/null @@ -1,149 +0,0 @@ -import Quick -import Nimble -import Nocilla -import PostHog - -class HTTPClientTest: QuickSpec { - override func spec() { - - var client: PHGHTTPClient! - let host = URL(string: "https://app.posthog.test")! - - beforeEach { - LSNocilla.sharedInstance().start() - client = PHGHTTPClient(requestFactory: nil) - } - afterEach { - LSNocilla.sharedInstance().clearStubs() - LSNocilla.sharedInstance().stop() - } - - describe("defaultRequestFactory") { - it("preserves url") { - let factory = PHGHTTPClient.defaultRequestFactory() - let url = URL(string: "https://app.posthog.test/batch") - let request = factory(url!) - expect(request.url) == url - } - } - - describe("sharedSessionUpload") { - it("works") { - let payload: [String: Any] = [ - "token": "some-token" - ] - - _ = stubRequest("POST", "https://app.posthog.test/decide" as LSMatcheable) - .withHeaders( - [ - "Content-Length": "22", - "Content-Type": "application/json" - ])! - .withBody( - "{\"token\":\"some-token\"}" as LSMatcheable) - - var done = false; - let task = client.sharedSessionUpload(payload, host: URL(string:"https://app.posthog.test/decide")!){ responseDict in - expect(responseDict).toNot(beNil()) - done = true - } failure: { error in - - expect(error).to(beNil()) - done = true - } - - expect(task).toNot(beNil()) - expect(done).toEventually(beTrue()) - } - } - - describe("upload") { - it("does not ask to retry for json error") { - let batch: [String: Any] = [ - // Dates cannot be serialized as is so the json serialzation will fail. - "sent_at": NSDate(), - "batch": [["type": "capture", "event": "foo"]], - ] - var done = false - let task = client.upload(batch, host: host) { retry in - expect(retry) == false - done = true - } - expect(task).to(beNil()) - expect(done).toEventually(beTrue()) - } - - let batch: [String: Any] = ["sent_at":"2016-07-19'T'19:25:06Z", "batch":[["type":"capture", "event":"foo"]], "api_key": "foobar"] - - it("does not ask to retry for 2xx response") { - _ = stubRequest("POST", "https://app.posthog.test/batch" as NSString) - .withHeader("User-Agent", "posthog-ios/" + PHGPostHog.version())! - .withJsonGzippedBody(batch as AnyObject) - .andReturn(200) - var done = false - let task = client.upload(batch, host: host) { retry in - expect(retry) == false - done = true - } - expect(done).toEventually(beTrue()) - expect(task.state).toEventually(equal(URLSessionTask.State.completed)) - } - - it("asks to retry for 3xx response") { - _ = stubRequest("POST", "https://app.posthog.test/batch" as NSString) - .withHeader("User-Agent", "posthog-ios/" + PHGPostHog.version())! - .withJsonGzippedBody(batch as AnyObject) - .andReturn(304) - var done = false - let task = client.upload(batch, host: host) { retry in - expect(retry) == true - done = true - } - expect(done).toEventually(beTrue()) - expect(task.state).toEventually(equal(URLSessionTask.State.completed)) - } - - it("does not ask to retry for 4xx response") { - _ = stubRequest("POST", "https://app.posthog.test/batch" as NSString) - .withHeader("User-Agent", "posthog-ios/" + PHGPostHog.version())! - .withJsonGzippedBody(batch as AnyObject) - .andReturn(401) - var done = false - let task = client.upload(batch, host: host) { retry in - expect(retry) == false - done = true - } - expect(done).toEventually(beTrue()) - expect(task.state).toEventually(equal(URLSessionTask.State.completed)) - } - - it("asks to retry for 429 response") { - _ = stubRequest("POST", "https://app.posthog.test/batch" as NSString) - .withHeader("User-Agent", "posthog-ios/" + PHGPostHog.version())! - .withJsonGzippedBody(batch as AnyObject) - .andReturn(429) - var done = false - let task = client.upload(batch, host: host) { retry in - expect(retry) == true - done = true - } - expect(done).toEventually(beTrue()) - expect(task.state).toEventually(equal(URLSessionTask.State.completed)) - } - - it("asks to retry for 5xx response") { - _ = stubRequest("POST", "https://app.posthog.test/batch" as NSString) - .withHeader("User-Agent", "posthog-ios/" + PHGPostHog.version())! - .withJsonGzippedBody(batch as AnyObject) - .andReturn(504) - var done = false - let task = client.upload(batch, host: host) { retry in - expect(retry) == true - done = true - } - expect(done).toEventually(beTrue()) - expect(task.state).toEventually(equal(URLSessionTask.State.completed)) - } - } - } -} diff --git a/PostHogTests/Info.plist b/PostHogTests/Info.plist deleted file mode 100644 index 6c6c23c43..000000000 --- a/PostHogTests/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - - diff --git a/PostHogTests/MiddlewareTests.swift b/PostHogTests/MiddlewareTests.swift deleted file mode 100644 index 9dae576d8..000000000 --- a/PostHogTests/MiddlewareTests.swift +++ /dev/null @@ -1,74 +0,0 @@ -import Quick -import Nimble -import PostHog - -// Changing event names and adding custom attributes -let customizeAllCaptureCalls = PHGBlockMiddleware { (context, next) in - if context.eventType == .capture { - next(context.modify { ctx in - guard let capture = ctx.payload as? PHGCapturePayload else { - return - } - let newEvent = "[New] \(capture.event)" - var newProps = capture.properties ?? [:] - newProps["customAttribute"] = "Hello" - newProps["nullTest"] = NSNull() - ctx.payload = PHGCapturePayload( - event: newEvent, - properties: newProps - ) - }) - } else { - next(context) - } -} - -// Simply swallows all calls and does not pass events downstream -let eatAllCalls = PHGBlockMiddleware { (context, next) in -} - -class MiddlewareTests: QuickSpec { - override func spec() { - it("receives events") { - let config = PHGPostHogConfiguration(apiKey: "foobar") - let passthrough = PHGPassthroughMiddleware() - config.middlewares = [ - passthrough, - ] - let posthog = PHGPostHog(configuration: config) - posthog.identify("testDistinctId1") - expect(passthrough.lastContext?.eventType) == PHGEventType.identify - let identify = passthrough.lastContext?.payload as? PHGIdentifyPayload - expect(identify?.distinctId) == "testDistinctId1" - } - - it("modifies and passes event to next") { - let config = PHGPostHogConfiguration(apiKey: "foobar") - let passthrough = PHGPassthroughMiddleware() - config.middlewares = [ - customizeAllCaptureCalls, - passthrough, - ] - let posthog = PHGPostHog(configuration: config) - posthog.capture("Purchase Success") - expect(passthrough.lastContext?.eventType) == PHGEventType.capture - let capture = passthrough.lastContext?.payload as? PHGCapturePayload - expect(capture?.event) == "[New] Purchase Success" - expect(capture?.properties?["customAttribute"] as? String) == "Hello" - let isNull = (capture?.properties?["nullTest"] is NSNull) - expect(isNull) == true - } - - it("expects event to be swallowed if next is not called") { - let config = PHGPostHogConfiguration(apiKey: "foobar") - let passthrough = PHGPassthroughMiddleware() - config.middlewares = [ - eatAllCalls, - passthrough, - ] - let posthog = PHGPostHog(configuration: config) - posthog.capture("Purchase Success") - expect(passthrough.lastContext).to(beNil()) - } - } -} diff --git a/PostHogTests/PostHogConfigTest.swift b/PostHogTests/PostHogConfigTest.swift new file mode 100644 index 000000000..10ff27d9f --- /dev/null +++ b/PostHogTests/PostHogConfigTest.swift @@ -0,0 +1,44 @@ +// +// PostHogConfigTest.swift +// PostHogTests +// +// Created by Manoel Aranda Neto on 30.10.23. +// + +import Foundation +import Nimble +@testable import PostHog +import Quick + +class PostHogConfigTest: QuickSpec { + override func spec() { + it("init config with default values") { + let config = PostHogConfig(apiKey: "123") + + expect(config.host) == URL(string: PostHogConfig.defaultHost) + expect(config.flushAt) == 20 + expect(config.maxQueueSize) == 1000 + expect(config.maxBatchSize) == 50 + expect(config.flushIntervalSeconds) == 30 + expect(config.dataMode) == .any + expect(config.sendFeatureFlagEvent) == true + expect(config.preloadFeatureFlags) == true + expect(config.captureApplicationLifecycleEvents) == true + expect(config.captureScreenViews) == true + expect(config.debug) == false + expect(config.optOut) == false + } + + it("init takes api key") { + let config = PostHogConfig(apiKey: "123") + + expect(config.apiKey) == "123" + } + + it("init takes host") { + let config = PostHogConfig(apiKey: "123", host: "localhost:9000") + + expect(config.host) == URL(string: "localhost:9000")! + } + } +} diff --git a/PostHogTests/PostHogContextTest.swift b/PostHogTests/PostHogContextTest.swift new file mode 100644 index 000000000..edef24f56 --- /dev/null +++ b/PostHogTests/PostHogContextTest.swift @@ -0,0 +1,60 @@ +// +// PostHogContextTest.swift +// PostHogTests +// +// Created by Manoel Aranda Neto on 30.10.23. +// + +import Foundation +import Nimble +@testable import PostHog +import Quick + +class PostHogContextTest: QuickSpec { + func getSut() -> PostHogContext { + var reachability: Reachability? + do { + reachability = try Reachability() + } catch { + // ignored + } + return PostHogContext(reachability) + } + + override func spec() { + it("returns static context") { + let sut = self.getSut() + + let context = sut.staticContext() + expect(context["$app_name"] as? String) == "xctest" + expect(context["$app_version"] as? String) != nil + expect(context["$app_build"] as? String) != nil + expect(context["$app_namespace"] as? String) == "com.apple.dt.xctest.tool" + #if os(iOS) || os(tvOS) + expect(context["$device_name"] as? String) != nil + expect(context["$os_name"] as? String) != nil + expect(context["$os_version"] as? String) != nil + expect(context["$device_type"] as? String) != nil + expect(context["$device_model"] as? String) != nil + expect(context["$device_manufacturer"] as? String) == "Apple" + #endif + } + + it("returns dynamic context") { + let sut = self.getSut() + + let context = sut.dynamicContext() + + #if os(iOS) || os(tvOS) + expect(context["$screen_width"] as? Float) != nil + expect(context["$screen_height"] as? Float) != nil + #endif + expect(context["$lib"] as? String) == "posthog-ios" + expect(context["$lib_version"] as? String) == postHogVersion + expect(context["$locale"] as? String) != nil + expect(context["$timezone"] as? String) != nil + expect(context["$network_wifi"] as? Bool) != nil + expect(context["$network_cellular"] as? Bool) != nil + } + } +} diff --git a/PostHogTests/PostHogFeatureFlagsTest.swift b/PostHogTests/PostHogFeatureFlagsTest.swift new file mode 100644 index 000000000..41b275c85 --- /dev/null +++ b/PostHogTests/PostHogFeatureFlagsTest.swift @@ -0,0 +1,140 @@ +// +// PostHogFeatureFlagsTest.swift +// PostHogTests +// +// Created by Manoel Aranda Neto on 30.10.23. +// + +import Foundation +import Nimble +@testable import PostHog +import Quick + +class PostHogFeatureFlagsTest: QuickSpec { + func getSut() -> PostHogFeatureFlags { + let config = PostHogConfig(apiKey: "123", host: "http://localhost:9001") + let storage = PostHogStorage(config) + let api = PostHogApi(config) + return PostHogFeatureFlags(config, storage, api) + } + + override func spec() { + var server: MockPostHogServer! + + beforeEach { + server = MockPostHogServer() + server.start() + } + afterEach { + server.stop() + } + + it("returns true for enabled flag - boolean") { + let sut = self.getSut() + let group = DispatchGroup() + group.enter() + + sut.loadFeatureFlags(distinctId: "distinctId", anonymousId: "anonymousId", groups: ["group": "value"], callback: { + group.leave() + }) + + group.wait() + + expect(sut.isFeatureEnabled("bool-value")) == true + } + + it("returns true for enabled flag - string") { + let sut = self.getSut() + let group = DispatchGroup() + group.enter() + + sut.loadFeatureFlags(distinctId: "distinctId", anonymousId: "anonymousId", groups: ["group": "value"], callback: { + group.leave() + }) + + group.wait() + + expect(sut.isFeatureEnabled("string-value")) == true + } + + it("returns false for disabled flag") { + let sut = self.getSut() + let group = DispatchGroup() + group.enter() + + sut.loadFeatureFlags(distinctId: "distinctId", anonymousId: "anonymousId", groups: ["group": "value"], callback: { + group.leave() + }) + + group.wait() + + expect(sut.isFeatureEnabled("disabled-flag")) == false + } + + it("returns feature flag value") { + let sut = self.getSut() + let group = DispatchGroup() + group.enter() + + sut.loadFeatureFlags(distinctId: "distinctId", anonymousId: "anonymousId", groups: ["group": "value"], callback: { + group.leave() + }) + + group.wait() + + expect(sut.getFeatureFlag("string-value") as? String) == "test" + } + + it("returns feature flag payload") { + let sut = self.getSut() + let group = DispatchGroup() + group.enter() + + sut.loadFeatureFlags(distinctId: "distinctId", anonymousId: "anonymousId", groups: ["group": "value"], callback: { + group.leave() + }) + + group.wait() + + expect(sut.getFeatureFlagPayload("number-value") as? Int) == 2 + } + + it("returns feature flag payload as dict") { + let sut = self.getSut() + let group = DispatchGroup() + group.enter() + + sut.loadFeatureFlags(distinctId: "distinctId", anonymousId: "anonymousId", groups: ["group": "value"], callback: { + group.leave() + }) + + expect(sut.getFeatureFlagPayload("payload-json") as? [String: String]) == ["foo": "bar"] + } + + it("merge flags if computed errors") { + let sut = self.getSut() + let group = DispatchGroup() + group.enter() + + sut.loadFeatureFlags(distinctId: "distinctId", anonymousId: "anonymousId", groups: ["group": "value"], callback: { + group.leave() + }) + + group.wait() + + server.errorsWhileComputingFlags = true + + let group2 = DispatchGroup() + group2.enter() + + sut.loadFeatureFlags(distinctId: "distinctId", anonymousId: "anonymousId", groups: ["group": "value"], callback: { + group2.leave() + }) + + group2.wait() + + expect(sut.isFeatureEnabled("new-flag")) == true + expect(sut.isFeatureEnabled("bool-value")) == true + } + } +} diff --git a/PostHogTests/PostHogFileBackedQueueTest.swift b/PostHogTests/PostHogFileBackedQueueTest.swift new file mode 100644 index 000000000..267a71c35 --- /dev/null +++ b/PostHogTests/PostHogFileBackedQueueTest.swift @@ -0,0 +1,177 @@ +// +// PostHogFileBackedQueueTest.swift +// PostHogTests +// +// Created by Manoel Aranda Neto on 30.10.23. +// + +import Foundation +import Nimble +@testable import PostHog +import Quick + +class PostHogFileBackedQueueTest: QuickSpec { + let eventJson = + """ + { + "properties": { + "$network_cellular": false, + "$groups": { + "some-group": "id:4" + }, + "$app_build": "1", + "$os_name": "iOS", + "$feature/multivariant": "payload", + "$screen_width": 852, + "$app_version": "1.0", + "$device_type": "Mobile", + "$active_feature_flags__0": "multivariant", + "$feature/4535-funnel-bar-viz": true, + "$network_wifi": true, + "$timezone": "Europe/Vienna", + "$device_id": "48DA3429-495A-4903-B347-F096FC31C3AB", + "$active_feature_flags": [ + "$feature/multivariant", + "$feature/testJson", + "$feature/4535-funnel-bar-viz", + "$feature/disabledFlag" + ], + "$active_feature_flags__3": "disabledFlag", + "$device_name": "iPhone", + "$app_name": "", + "$app_namespace": "com.posthog.CocoapodsExample", + "$locale": "en-US", + "$feature/disabledFlag": true, + "$active_feature_flags__1": "testJson", + "$screen_height": 393, + "$device_model": "arm64", + "$device_manufacturer": "Apple", + "$feature/testJson": "theInteger", + "$lib_version": "2.1.0", + "$os_version": "17.0.1", + "$lib": "posthog-ios", + "$active_feature_flags__2": "4535-funnel-bar-viz" + }, + "timestamp": "2023-10-25T14:14:04.407Z", + "message_id": "5CE069F8-E967-4B47-9D89-207EF7519453", + "event": "Cocoapods Example Button", + "distinct_id": "Prateek" + } + """ + + func getSut() -> PostHogFileBackedQueue { + let baseUrl = applicationSupportDirectoryURL() + let oldURL = baseUrl.appendingPathComponent("oldQueue") + let newURL = baseUrl.appendingPathComponent("queue") + + return PostHogFileBackedQueue(queue: newURL, oldQueue: oldURL) + } + + override func spec() { + it("create folder and init queue") { + let sut = self.getSut() + + expect(sut.depth) == 0 + expect(FileManager.default.fileExists(atPath: sut.queue.path)) == true + + sut.clear() + } + + it("load cached files into memory") { + let baseUrl = applicationSupportDirectoryURL() + let newURL = baseUrl.appendingPathComponent("queue") + try FileManager.default.createDirectory(atPath: newURL.path, withIntermediateDirectories: true) + + let eventURL = newURL.appendingPathComponent("1698236044.407") + let eventsData = self.eventJson.data(using: .utf8)! + try eventsData.write(to: eventURL) + + expect(FileManager.default.fileExists(atPath: eventURL.path)) == true + + let sut = self.getSut() + + expect(sut.depth) == 1 + let items = sut.peek(1) + expect(items.first) != nil + + sut.clear() + } + + it("delete from queue and disk") { + let baseUrl = applicationSupportDirectoryURL() + let newURL = baseUrl.appendingPathComponent("queue") + try FileManager.default.createDirectory(atPath: newURL.path, withIntermediateDirectories: true) + + let eventURL = newURL.appendingPathComponent("1698236044.407") + let eventsData = self.eventJson.data(using: .utf8)! + try eventsData.write(to: eventURL) + + expect(FileManager.default.fileExists(atPath: eventURL.path)) == true + + let sut = self.getSut() + + sut.delete(index: 0) + + expect(sut.depth) == 0 + expect(FileManager.default.fileExists(atPath: eventURL.path)) == false + + sut.clear() + } + + it("pop from queue and disk") { + let baseUrl = applicationSupportDirectoryURL() + let newURL = baseUrl.appendingPathComponent("queue") + try FileManager.default.createDirectory(atPath: newURL.path, withIntermediateDirectories: true) + + let eventURL = newURL.appendingPathComponent("1698236044.407") + let eventsData = self.eventJson.data(using: .utf8)! + try eventsData.write(to: eventURL) + + expect(FileManager.default.fileExists(atPath: eventURL.path)) == true + + let sut = self.getSut() + + sut.pop(1) + + expect(sut.depth) == 0 + expect(FileManager.default.fileExists(atPath: eventURL.path)) == false + + sut.clear() + } + + it("add to queue and disk") { + let baseUrl = applicationSupportDirectoryURL() + let newURL = baseUrl.appendingPathComponent("queue") + try FileManager.default.createDirectory(atPath: newURL.path, withIntermediateDirectories: true) + + let eventsData = self.eventJson.data(using: .utf8)! + + let sut = self.getSut() + + sut.add(eventsData) + + let items = try FileManager.default.contentsOfDirectory(atPath: newURL.path) + expect(sut.depth) == 1 + expect(items.count) == 1 + + sut.clear() + } + + it("clear queue and disk") { + let baseUrl = applicationSupportDirectoryURL() + let newURL = baseUrl.appendingPathComponent("queue") + try FileManager.default.createDirectory(atPath: newURL.path, withIntermediateDirectories: true) + + let eventsData = self.eventJson.data(using: .utf8)! + + let sut = self.getSut() + + sut.add(eventsData) + sut.clear() + + let items = try FileManager.default.contentsOfDirectory(atPath: newURL.path) + expect(sut.depth) == 0 + expect(items.count) == 0 + } + } +} diff --git a/PostHogTests/PostHogLegacyQueueTest.swift b/PostHogTests/PostHogLegacyQueueTest.swift new file mode 100644 index 000000000..2da6f7b65 --- /dev/null +++ b/PostHogTests/PostHogLegacyQueueTest.swift @@ -0,0 +1,133 @@ +// +// PostHogLegacyQueueTest.swift +// PostHogTests +// +// Created by Manoel Aranda Neto on 30.10.23. +// + +import Foundation +import Nimble +@testable import PostHog +import Quick + +class PostHogLegacyQueueTest: QuickSpec { + override func spec() { + it("migrate old queue to new queue") { + let baseUrl = applicationSupportDirectoryURL() + try FileManager.default.createDirectory(atPath: baseUrl.path, withIntermediateDirectories: true) + + let newURL = baseUrl.appendingPathComponent("queue") + try FileManager.default.createDirectory(atPath: newURL.path, withIntermediateDirectories: true) + + let oldURL = baseUrl.appendingPathComponent("oldQueue") + + let eventsArray = + """ + [ + { + "properties": { + "$network_cellular": false, + "$groups": { + "some-group": "id:4" + }, + "$app_build": "1", + "$os_name": "iOS", + "$feature/multivariant": "payload", + "$screen_width": 852, + "$app_version": "1.0", + "$device_type": "Mobile", + "$active_feature_flags__0": "multivariant", + "$feature/4535-funnel-bar-viz": true, + "$network_wifi": true, + "$timezone": "Europe/Vienna", + "$device_id": "48DA3429-495A-4903-B347-F096FC31C3AB", + "$active_feature_flags": [ + "$feature/multivariant", + "$feature/testJson", + "$feature/4535-funnel-bar-viz", + "$feature/disabledFlag" + ], + "$active_feature_flags__3": "disabledFlag", + "$device_name": "iPhone", + "$app_name": "", + "$app_namespace": "com.posthog.CocoapodsExample", + "$locale": "en-US", + "$feature/disabledFlag": true, + "$active_feature_flags__1": "testJson", + "$screen_height": 393, + "$device_model": "arm64", + "$device_manufacturer": "Apple", + "$feature/testJson": "theInteger", + "$lib_version": "2.1.0", + "$os_version": "17.0.1", + "$lib": "posthog-ios", + "$active_feature_flags__2": "4535-funnel-bar-viz" + }, + "timestamp": "2023-10-25T14:14:04.407Z", + "message_id": "5CE069F8-E967-4B47-9D89-207EF7519453", + "event": "Cocoapods Example Button", + "distinct_id": "Prateek" + } + ] + """ + + let eventsData = eventsArray.data(using: .utf8)! + try eventsData.write(to: oldURL) + + expect(FileManager.default.fileExists(atPath: oldURL.path)) == true + + migrateOldQueue(queue: newURL, oldQueue: oldURL) + + expect(FileManager.default.fileExists(atPath: oldURL.path)) == false + + let items = try FileManager.default.contentsOfDirectory(atPath: newURL.path) + + let eventURL = newURL.appendingPathComponent(items[0]) + expect(FileManager.default.fileExists(atPath: eventURL.path)) == true + + let eventData = try Data(contentsOf: eventURL) + let eventObject = try JSONSerialization.jsonObject(with: eventData, options: .allowFragments) as? [String: Any] + + expect(eventObject!["distinct_id"] as? String) == "Prateek" + expect(eventObject!["event"] as? String) == "Cocoapods Example Button" + expect(eventObject!["message_id"] as? String) == "5CE069F8-E967-4B47-9D89-207EF7519453" + expect(eventObject!["timestamp"] as? String) == "2023-10-25T14:14:04.407Z" + expect(eventObject!["properties"] as? [String: Any]) != nil + + deleteSafely(oldURL) + deleteSafely(newURL) + } + + it("ignore and delete corrupted file") { + let baseUrl = applicationSupportDirectoryURL() + try FileManager.default.createDirectory(atPath: baseUrl.path, withIntermediateDirectories: true) + + let newURL = baseUrl.appendingPathComponent("queue") + try FileManager.default.createDirectory(atPath: newURL.path, withIntermediateDirectories: true) + + let oldURL = baseUrl.appendingPathComponent("oldQueue") + + let eventsArray = + """ + [ + i am broken + ] + """ + + let eventsData = eventsArray.data(using: .utf8)! + try eventsData.write(to: oldURL) + + expect(FileManager.default.fileExists(atPath: oldURL.path)) == true + + migrateOldQueue(queue: newURL, oldQueue: oldURL) + + expect(FileManager.default.fileExists(atPath: oldURL.path)) == false + + let items = try FileManager.default.contentsOfDirectory(atPath: newURL.path) + expect(items.isEmpty) == true + + deleteSafely(oldURL) + deleteSafely(newURL) + } + } +} diff --git a/PostHogTests/PostHogQueueTest.swift b/PostHogTests/PostHogQueueTest.swift new file mode 100644 index 000000000..109acf3b2 --- /dev/null +++ b/PostHogTests/PostHogQueueTest.swift @@ -0,0 +1,68 @@ +// +// PostHogQueueTest.swift +// PostHogTests +// +// Created by Manoel Aranda Neto on 30.10.23. +// + +import Foundation +import Nimble +@testable import PostHog +import Quick +import XCTest + +class PostHogQueueTest: QuickSpec { + func getSut() -> PostHogQueue { + let config = PostHogConfig(apiKey: "123", host: "http://localhost:9001") + config.flushAt = 1 + let storage = PostHogStorage(config) + let api = PostHogApi(config) + return PostHogQueue(config, storage, api, nil) + } + + override func spec() { + var server: MockPostHogServer! + + beforeEach { + server = MockPostHogServer() + server.start() + } + afterEach { + server.stop() + } + + it("add item to queue") { + let sut = self.getSut() + + let event = PostHogEvent(event: "event", distinctId: "distinctId") + sut.add(event) + + expect(sut.depth) == 1 + + let events = getBatchedEvents(server) + expect(events.count) == 1 + + expect(sut.depth) == 0 + + sut.clear() + } + + it("add item to queue and flush respecting flushAt") { + let sut = self.getSut() + + let event = PostHogEvent(event: "event", distinctId: "distinctId") + let event2 = PostHogEvent(event: "event2", distinctId: "distinctId2") + sut.add(event) + sut.add(event2) + + expect(sut.depth) == 2 + + let events = getBatchedEvents(server) + expect(events.count) == 1 + + expect(sut.depth) == 1 + + sut.clear() + } + } +} diff --git a/PostHogTests/PostHogSDKTest.swift b/PostHogTests/PostHogSDKTest.swift new file mode 100644 index 000000000..196b53cbd --- /dev/null +++ b/PostHogTests/PostHogSDKTest.swift @@ -0,0 +1,528 @@ +// +// PostHogSDKTest.swift +// PostHogTests +// +// Created by Manoel Aranda Neto on 31.10.23. +// + +import Foundation +import Nimble +import Quick + +@testable import PostHog + +class PostHogSDKTest: QuickSpec { + func getSut(preloadFeatureFlags: Bool = false, + sendFeatureFlagEvent: Bool = false, + flushAt: Int = 1, + optOut: Bool = false) -> PostHogSDK + { + let config = PostHogConfig(apiKey: "123", host: "http://localhost:9001") + config.flushAt = flushAt + config.preloadFeatureFlags = preloadFeatureFlags + config.sendFeatureFlagEvent = sendFeatureFlagEvent + config.disableReachabilityForTesting = true + config.disableQueueTimerForTesting = true + config.optOut = optOut + return PostHogSDK.with(config) + } + + override func spec() { + var server: MockPostHogServer! + + func deleteDefaults() { + let userDefaults = UserDefaults.standard + userDefaults.removeObject(forKey: "PHGVersionKey") + userDefaults.removeObject(forKey: "PHGBuildKeyV2") + userDefaults.synchronize() + + deleteSafely(applicationSupportDirectoryURL()) + } + + beforeEach { + deleteDefaults() + server = MockPostHogServer() + server.start() + } + afterEach { + server.stop() + server = nil + } + + it("captures the capture event") { + let sut = self.getSut() + + sut.capture("test event", + properties: ["foo": "bar"], + userProperties: ["userProp": "value"], + userPropertiesSetOnce: ["userPropOnce": "value"], + groupProperties: ["groupProp": "value"]) + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + + let event = events.first! + expect(event.event) == "test event" + + expect(event.properties["foo"] as? String) == "bar" + + let set = event.properties["$set"] as? [String: Any] ?? [:] + expect(set["userProp"] as? String) == "value" + + let setOnce = event.properties["$set_once"] as? [String: Any] ?? [:] + expect(setOnce["userPropOnce"] as? String) == "value" + + let groupProps = event.properties["$groups"] as? [String: Any] ?? [:] + expect(groupProps["groupProp"] as? String) == "value" + + sut.reset() + sut.close() + } + + it("captures an identify event") { + let sut = self.getSut() + + sut.identify("distinctId", + userProperties: ["userProp": "value"], + userPropertiesSetOnce: ["userPropOnce": "value"]) + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + + let event = events.first! + expect(event.event) == "$identify" + + expect(event.distinctId) == "distinctId" + let anonId = sut.getAnonymousId() + expect(event.properties["$anon_distinct_id"] as? String) == anonId + + let set = event.properties["$set"] as? [String: Any] ?? [:] + expect(set["userProp"] as? String) == "value" + + let setOnce = event.properties["$set_once"] as? [String: Any] ?? [:] + expect(setOnce["userPropOnce"] as? String) == "value" + + sut.reset() + sut.close() + } + + it("captures an alias event") { + let sut = self.getSut() + + sut.alias("theAlias") + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + + let event = events.first! + expect(event.event) == "$create_alias" + + expect(event.properties["alias"] as? String) == "theAlias" + + sut.reset() + sut.close() + } + + it("captures a screen event") { + let sut = self.getSut() + + sut.screen("theScreen", properties: ["prop": "value"]) + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + + let event = events.first! + expect(event.event) == "$screen" + + expect(event.properties["$screen_name"] as? String) == "theScreen" + expect(event.properties["prop"] as? String) == "value" + + sut.reset() + sut.close() + } + + it("captures a group event") { + let sut = self.getSut() + + sut.group(type: "some-type", key: "some-key", groupProperties: [ + "name": "some-company-name", + ]) + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + + let groupEvent = events.first! + expect(groupEvent.event) == "$groupidentify" + expect(groupEvent.properties["$group_type"] as? String?) == "some-type" + expect(groupEvent.properties["$group_key"] as? String?) == "some-key" + expect((groupEvent.properties["$group_set"] as? [String: String])?["name"] as? String) == "some-company-name" + + sut.reset() + sut.close() + } + + it("setups default IDs") { + let sut = self.getSut() + + expect(sut.getAnonymousId()).toNot(beNil()) + expect(sut.getDistinctId()) == sut.getAnonymousId() + + sut.reset() + sut.close() + } + + it("setups optOut") { + let sut = self.getSut() + + sut.optOut() + + expect(sut.isOptOut()) == true + + sut.optIn() + + expect(sut.isOptOut()) == false + + sut.reset() + sut.close() + } + + it("sets opt out via config") { + let sut = self.getSut(optOut: true) + + sut.optOut() + + expect(sut.isOptOut()) == true + + sut.reset() + sut.close() + } + + it("calls reloadFeatureFlags") { + let sut = self.getSut() + + let group = DispatchGroup() + group.enter() + + sut.reloadFeatureFlags { + group.leave() + } + + group.wait() + + expect(sut.isFeatureEnabled("bool-value")) == true + + sut.reset() + sut.close() + } + + it("identify sets distinct and anon Ids") { + let sut = self.getSut() + + let distId = sut.getDistinctId() + + sut.identify("newDistinctId") + + expect(sut.getDistinctId()) == "newDistinctId" + expect(sut.getAnonymousId()) == distId + + sut.reset() + sut.close() + } + + it("loads feature flags automatically") { + let sut = self.getSut(preloadFeatureFlags: true) + + waitDecideRequest(server) + expect(sut.isFeatureEnabled("bool-value")) == true + + sut.reset() + sut.close() + } + + it("send feature flag event for isFeatureEnabled when enabled") { + let sut = self.getSut(preloadFeatureFlags: true, sendFeatureFlagEvent: true) + + waitDecideRequest(server) + expect(sut.isFeatureEnabled("bool-value")) == true + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + + let event = events.first! + expect(event.event) == "$feature_flag_called" + expect(event.properties["$feature_flag"] as? String) == "bool-value" + expect(event.properties["$feature_flag_response"] as? Bool) == true + + sut.reset() + sut.close() + } + + it("send feature flag event for getFeatureFlag when enabled") { + let sut = self.getSut(preloadFeatureFlags: true, sendFeatureFlagEvent: true) + + waitDecideRequest(server) + expect(sut.getFeatureFlag("bool-value") as? Bool) == true + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + + let event = events.first! + expect(event.event) == "$feature_flag_called" + expect(event.properties["$feature_flag"] as? String) == "bool-value" + expect(event.properties["$feature_flag_response"] as? Bool) == true + + sut.reset() + sut.close() + } + + it("capture AppBackgrounded") { + let sut = self.getSut() + + sut.captureAppBackgrounded() + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + + let event = events.first! + expect(event.event) == "Application Backgrounded" + + sut.reset() + sut.close() + } + + it("capture AppInstalled") { + let sut = self.getSut() + + sut.captureAppInstalled() + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + + let event = events.first! + expect(event.event) == "Application Installed" + expect(event.properties["version"] as? String) != nil + expect(event.properties["build"] as? String) != nil + + sut.reset() + sut.close() + } + + it("capture AppUpdated") { + let sut = self.getSut() + + let userDefaults = UserDefaults.standard + userDefaults.setValue("1.0.0", forKey: "PHGVersionKey") + userDefaults.setValue("1", forKey: "PHGBuildKeyV2") + userDefaults.synchronize() + + sut.captureAppInstalled() + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + + let event = events.first! + expect(event.event) == "Application Updated" + expect(event.properties["version"] as? String) != nil + expect(event.properties["build"] as? String) != nil + expect(event.properties["previous_version"] as? String) != nil + expect(event.properties["previous_build"] as? String) != nil + + sut.reset() + sut.close() + } + + it("capture AppOpenedFromBackground") { + let sut = self.getSut() + + sut.captureAppOpenedFromBackground() + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + + let event = events.first! + expect(event.event) == "Application Opened" + expect(event.properties["from_background"] as? Bool) == false + + sut.reset() + sut.close() + } + + it("capture AppOpenedFromBackground") { + let sut = self.getSut(flushAt: 2) + + sut.captureAppOpenedFromBackground() + sut.captureAppOpenedFromBackground() + + let events = getBatchedEvents(server) + + expect(events.count) == 2 + + let event = events.last! + expect(event.event) == "Application Opened" + expect(event.properties["from_background"] as? Bool) == true + + sut.reset() + sut.close() + } + + it("capture captureAppOpened") { + let sut = self.getSut() + + sut.captureAppOpened() + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + + let event = events.first! + expect(event.event) == "Application Opened" + expect(event.properties["from_background"] as? Bool) == false + expect(event.properties["version"] as? String) != nil + expect(event.properties["build"] as? String) != nil + + sut.reset() + sut.close() + } + + it("reloadFeatureFlags adds groups if any") { + let sut = self.getSut() + + sut.group(type: "some-type", key: "some-key", groupProperties: [ + "name": "some-company-name", + ]) + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + + sut.reloadFeatureFlags() + + let requests = getDecideRequest(server) + + expect(requests.count) == 1 + let request = requests.first + + let groups = request!["$groups"] as? [String: Any] + expect(groups!["some-type"] as? String) == "some-key" + + sut.reset() + sut.close() + } + + it("merge groups when group is called") { + let sut = self.getSut(flushAt: 3) + + sut.group(type: "some-type", key: "some-key") + + sut.group(type: "some-type-2", key: "some-key-2") + + sut.capture("event") + + let events = getBatchedEvents(server) + + expect(events.count) == 3 + let event = events.last! + + let groups = event.properties["$groups"] as? [String: Any] + expect(groups!["some-type"] as? String) == "some-key" + expect(groups!["some-type-2"] as? String) == "some-key-2" + + sut.reset() + sut.close() + } + + it("register and unregister properties") { + let sut = self.getSut(flushAt: 1) + + sut.register(["test1": "test"]) + sut.register(["test2": "test"]) + sut.unregister("test2") + sut.register(["test3": "test"]) + + sut.capture("event") + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + let event = events.last! + + expect(event.properties["test1"] as? String) == "test" + expect(event.properties["test3"] as? String) == "test" + expect(event.properties["test2"] as? String) == nil + + sut.reset() + sut.close() + } + + it("add active feature flags as part of the event") { + let sut = self.getSut() + + sut.reloadFeatureFlags() + waitDecideRequest(server) + + sut.capture("event") + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + let event = events.first! + + let activeFlags = event.properties["$active_feature_flags"] as? [Any] ?? [] + expect(activeFlags.contains { $0 as? String == "bool-value" }) == true + expect(activeFlags.contains { $0 as? String == "disabled-flag" }) == false + + expect(event.properties["$feature/bool-value"] as? Bool) == true + expect(event.properties["$feature/disabled-flag"] as? Bool) == false + + sut.reset() + sut.close() + } + + it("sanitize properties") { + let sut = self.getSut(flushAt: 1) + + sut.register(["boolIsOk": true, + "test5": UserDefaults.standard]) + + sut.capture("test event", + properties: ["foo": "bar", + "test1": UserDefaults.standard, + "arrayIsOk": [1, 2, 3], + "dictIsOk": ["1": "one"]], + userProperties: ["userProp": "value", + "test2": UserDefaults.standard], + userPropertiesSetOnce: ["userPropOnce": "value", + "test3": UserDefaults.standard], + groupProperties: ["groupProp": "value", + "test4": UserDefaults.standard]) + + let events = getBatchedEvents(server) + + expect(events.count) == 1 + let event = events.first! + + expect(event.properties["test1"]) == nil + expect(event.properties["test2"]) == nil + expect(event.properties["test3"]) == nil + expect(event.properties["test4"]) == nil + expect(event.properties["test5"]) == nil + expect(event.properties["arrayIsOk"]) != nil + expect(event.properties["dictIsOk"]) != nil + expect(event.properties["boolIsOk"]) != nil + + sut.reset() + sut.close() + } + } +} diff --git a/PostHogTests/PostHogSessionManagerTest.swift b/PostHogTests/PostHogSessionManagerTest.swift new file mode 100644 index 000000000..3ae343116 --- /dev/null +++ b/PostHogTests/PostHogSessionManagerTest.swift @@ -0,0 +1,49 @@ +// +// PostHogSessionManagerTest.swift +// PostHogTests +// +// Created by Ben White on 22.03.23. +// + +import Foundation +import Nimble +@testable import PostHog +import Quick + +class PostHogSessionManagerTest: QuickSpec { + func getSut() -> PostHogSessionManager { + let config = PostHogConfig(apiKey: "123") + return PostHogSessionManager(config) + } + + override func spec() { + it("Generates an anonymousId") { + let sut = self.getSut() + + let anonymousId = sut.getAnonymousId() + expect(anonymousId) != nil + let secondAnonymousId = sut.getAnonymousId() + expect(secondAnonymousId) == anonymousId + + sut.reset() + } + + it("Uses the anonymousId for distinctId if not set") { + let sut = self.getSut() + + let anonymousId = sut.getAnonymousId() + let distinctId = sut.getDistinctId() + expect(distinctId) == anonymousId + + let idToSet = UUID().uuidString + sut.setDistinctId(idToSet) + let newAnonymousId = sut.getAnonymousId() + let newDistinctId = sut.getDistinctId() + expect(newAnonymousId) == anonymousId + expect(newAnonymousId) != newDistinctId + expect(newDistinctId) == idToSet + + sut.reset() + } + } +} diff --git a/PostHogTests/PostHogStorageTest.swift b/PostHogTests/PostHogStorageTest.swift new file mode 100644 index 000000000..65f30ef23 --- /dev/null +++ b/PostHogTests/PostHogStorageTest.swift @@ -0,0 +1,97 @@ +// +// PostHogStorageTest.swift +// PostHogTests +// +// Created by Ben White on 08.02.23. +// + +import Foundation +import Nimble +@testable import PostHog +import Quick + +class PostHogStorageTest: QuickSpec { + func getSut() -> PostHogStorage { + let config = PostHogConfig(apiKey: "123") + return PostHogStorage(config) + } + + override func spec() { + it("returns the support dir URL") { + let url = applicationSupportDirectoryURL() + expect(url).toNot(beNil()) + expect(url.pathComponents[url.pathComponents.count - 2]) == "Application Support" + expect(url.lastPathComponent) == Bundle.main.bundleIdentifier + } + + it("creates folder if none exists") { + let url = applicationSupportDirectoryURL() + try? FileManager.default.removeItem(at: url) + + expect(FileManager.default.fileExists(atPath: url.path)) == false + + let sut = self.getSut() + + expect(FileManager.default.fileExists(atPath: sut.appFolderUrl.path)) == true + + sut.reset() + } + + it("persists and loads string") { + let sut = self.getSut() + + let str = "san francisco" + sut.setString(forKey: .distinctId, contents: str) + + expect(sut.getString(forKey: .distinctId)) == str + + sut.remove(key: .distinctId) + expect(sut.getString(forKey: .distinctId)).to(beNil()) + + sut.reset() + } + + it("persists and loads bool") { + let sut = self.getSut() + + sut.setBool(forKey: .optOut, contents: true) + + expect(sut.getBool(forKey: .optOut)) == true + + sut.remove(key: .optOut) + expect(sut.getString(forKey: .optOut)).to(beNil()) + + sut.reset() + } + + it("persists and loads dictionary") { + let sut = self.getSut() + + let dict = [ + "san francisco": "tech", + "new york": "finance", + "paris": "fashion", + ] + sut.setDictionary(forKey: .distinctId, contents: dict) + expect(sut.getDictionary(forKey: .distinctId) as? [String: String]) == dict + + sut.remove(key: .distinctId) + expect(sut.getDictionary(forKey: .distinctId)).to(beNil()) + + sut.reset() + } + + it("saves file to disk and removes from disk") { + let sut = self.getSut() + + let url = sut.url(forKey: .distinctId) + expect(try? url.checkResourceIsReachable()).to(beNil()) + sut.setString(forKey: .distinctId, contents: "sloth") + expect(try! url.checkResourceIsReachable()) == true + sut.remove(key: .distinctId) + expect(try? url.checkResourceIsReachable()).to(beNil()) + + sut.reset() + } + } +} diff --git a/PostHogTests/PostHogTests-Bridging-Header.h b/PostHogTests/PostHogTests-Bridging-Header.h deleted file mode 100644 index 1f12f676a..000000000 --- a/PostHogTests/PostHogTests-Bridging-Header.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// Use this file to import your target's public headers that you would like to expose to Swift. -// - -#import "PHGPayloadManager.h" -#import "PHGAES256Crypto.h" -#import "PHGFileStorage.h" -#import "PHGUserDefaultsStorage.h" -#import "NSData+PHGGZIP.h" -#import "PHGStoreKitCapturer.h" -#import "UIViewController+PHGScreen.h" -#import "PHGPostHogUtils.h" -#import "PHGPayloadManager.h" -#import "PHGUtils.h" - -#import "NSData+PHGGUNZIPP.h" -#import "LSMatcher.h" -#import "ObjC.h" diff --git a/PostHogTests/PostHogTests.swift b/PostHogTests/PostHogTests.swift deleted file mode 100644 index e9949aa31..000000000 --- a/PostHogTests/PostHogTests.swift +++ /dev/null @@ -1,234 +0,0 @@ -import Quick -import Nimble -import PostHog - -class PostHogTests: QuickSpec { - override func spec() { - let config = PHGPostHogConfiguration(apiKey: "foobar") - var posthog: PHGPostHog! - var testMiddleware: TestMiddleware! - var testApplication: TestApplication! - - beforeEach { - testMiddleware = TestMiddleware() - config.middlewares = [testMiddleware] - testApplication = TestApplication() - config.application = testApplication - config.captureApplicationLifecycleEvents = true - config.preloadFeatureFlags = true - - UserDefaults.standard.set("test PHGQueue should be removed", forKey: "PHGQueue") - expect(UserDefaults.standard.string(forKey: "PHGQueue")).toNot(beNil()) - - posthog = PHGPostHog(configuration: config) - } - - afterEach { - posthog.reset() - } - - it("initialized correctly") { - expect(posthog.configuration.flushAt) == 20 - expect(posthog.configuration.flushInterval) == 30 - expect(posthog.configuration.maxQueueSize) == 1000 - expect(posthog.configuration.apiKey) == "foobar" - expect(posthog.configuration.host) == URL(string: "https://app.posthog.com") - expect(posthog.configuration.shouldUseLocationServices) == false - expect(posthog.configuration.shouldUseBluetooth) == false - expect(posthog.configuration.libraryName) == "posthog-ios" - expect(posthog.configuration.libraryVersion) == PHGPostHog.version() - expect(posthog.configuration.httpSessionDelegate).to(beNil()) - expect(posthog.getAnonymousId()).toNot(beNil()) - } - - it("loads feature flags on init") { - expect(testMiddleware.lastContext?.eventType) == .reloadFeatureFlags - } - - it("doesn't load feature flags on init when configured") { - config.preloadFeatureFlags = false - testMiddleware = TestMiddleware() - config.middlewares = [testMiddleware] - - posthog = PHGPostHog(configuration: config) - expect(testMiddleware.lastContext).to(beNil()) - } - - it("initialized correctly with api host") { - let config = PHGPostHogConfiguration(apiKey: "foobar", host: "https://testapp.posthog.test") - config.libraryName = "posthog-ios-test" - config.libraryVersion = "posthog-ios-version" - - posthog = PHGPostHog(configuration: config) - expect(posthog.configuration.flushAt) == 20 - expect(posthog.configuration.flushInterval) == 30 - expect(posthog.configuration.maxQueueSize) == 1000 - expect(posthog.configuration.apiKey) == "foobar" - expect(posthog.configuration.host) == URL(string: "https://testapp.posthog.test") - expect(posthog.configuration.shouldUseLocationServices) == false - expect(posthog.configuration.shouldUseBluetooth) == false - expect(posthog.configuration.libraryVersion) == "posthog-ios-version" - expect(posthog.configuration.libraryName) == "posthog-ios-test" - expect(posthog.configuration.httpSessionDelegate).to(beNil()) - expect(posthog.getAnonymousId()).toNot(beNil()) - - let integration = posthog.test_payloadManager()?.test_postHogIntegration() - expect(integration!.liveContext()["$lib"] as? String) == "posthog-ios-test" - expect(integration!.liveContext()["$lib_version"] as? String) == "posthog-ios-version" - } - - it("clears PHGQueue from UserDefaults after initialized") { - expect(UserDefaults.standard.string(forKey: "PHGQueue")).toEventually(beNil()) - } - - it("persists anonymousId") { - let posthog2 = PHGPostHog(configuration: config) - expect(posthog.getAnonymousId()) == posthog2.getAnonymousId() - } - - it("persists distinctId") { - posthog.identify("testDistinctId1") - - let posthog2 = PHGPostHog(configuration: config) - - expect(posthog.test_payloadManager()?.test_postHogIntegration()?.test_distinctId()) == "testDistinctId1" - expect(posthog2.test_payloadManager()?.test_postHogIntegration()?.test_distinctId()) == "testDistinctId1" - } - - it("fires Application Opened for UIApplicationDidFinishLaunching") { - testMiddleware.swallowEvent = true - NotificationCenter.default.post(name: NSNotification.Name.UIApplicationDidFinishLaunching, object: testApplication, userInfo: [ - UIApplication.LaunchOptionsKey.sourceApplication: "testApp", - UIApplication.LaunchOptionsKey.url: "test://test", - ]) - - let event = testMiddleware.lastContext?.payload as? PHGCapturePayload - expect(event?.event) == "Application Opened" - expect(event?.properties?["from_background"] as? Bool) == false - expect(event?.properties?["referring_application"] as? String) == "testApp" - expect(event?.properties?["url"] as? String) == "test://test" - } - - it("fires Application Opened during UIApplicationWillEnterForeground") { - testMiddleware.swallowEvent = true - NotificationCenter.default.post(name: NSNotification.Name.UIApplicationWillEnterForeground, object: testApplication) - let event = testMiddleware.lastContext?.payload as? PHGCapturePayload - expect(event?.event) == "Application Opened" - expect(event?.properties?["from_background"] as? Bool) == true - } - - it("fires Application Backgrounded during UIApplicationDidEnterBackground") { - testMiddleware.swallowEvent = true - NotificationCenter.default.post(name: NSNotification.Name.UIApplicationDidEnterBackground, object: testApplication) - let event = testMiddleware.lastContext?.payload as? PHGCapturePayload - expect(event?.event) == "Application Backgrounded" - } - - it("flushes when UIApplicationDidEnterBackground is fired") { - posthog.capture("test") - NotificationCenter.default.post(name: NSNotification.Name.UIApplicationDidEnterBackground, object: testApplication) - expect(testApplication.backgroundTasks.count).toEventually(equal(1)) - expect(testApplication.backgroundTasks[0].isEnded).toEventually(beFalse()) - } - - it("respects maxQueueSize") { - let max = 72 - config.maxQueueSize = UInt(max) - - for i in 1...max * 2 { - posthog.capture("test #\(i)") - } - - let integration = posthog.test_payloadManager()?.test_postHogIntegration() - expect(integration).notTo(beNil()) - - posthog.flush() - waitUntil(timeout: DispatchTimeInterval.seconds(60)) {done in - let queue = DispatchQueue(label: "test") - - queue.async { - while(integration?.test_queue()?.count != max) { - sleep(1) - } - - done() - } - } - } - - it("protocol conformance should not interfere with UIApplication interface") { - // In Xcode8/iOS10, UIApplication.h typedefs UIBackgroundTaskIdentifier as NSUInteger, - // whereas Swift has UIBackgroundTaskIdentifier typealiaed to Int. - // This is likely due to a custom Swift mapping for UIApplication which got out of sync. - // If we extract the exact UIApplication method names in PHGApplicationProtocol, - // it will cause a type mismatch between the return value from beginBackgroundTask - // and the argument for endBackgroundTask. - // This would impact all code in a project that imports the framework. - // Note that this doesn't appear to be an issue any longer in Xcode9b3. - let task = UIApplication.shared.beginBackgroundTask(expirationHandler: nil) - UIApplication.shared.endBackgroundTask(task) - } - -// it("flushes using flushTimer") { -// let integration = posthog.test_payloadManager()?.test_postHogIntegration() -// -// let timer = posthog -// .test_payloadManager()? -// .test_postHogIntegration()? -// .test_flushTimer() -// -// expect(timer).toNot(beNil()) -// expect(timer?.timeInterval) == config.flushInterval -// -// posthog.capture("test") -// -// expect(integration?.test_flushTimer()).toEventuallyNot(beNil()) -// expect(integration?.test_batchRequest()).to(beNil()) -// -// timer?.fire() -// -// -// expect(integration?.test_batchRequest()).toEventuallyNot(beNil()) -// } - - it("respects flushInterval") { - let timer = posthog - .test_payloadManager()? - .test_postHogIntegration()? - .test_flushTimer() - - expect(timer).toNot(beNil()) - expect(timer?.timeInterval) == config.flushInterval - } - - it("redacts sensible URLs from deep links capturing") { - testMiddleware.swallowEvent = true - posthog.configuration.captureDeepLinks = true - posthog.open(URL(string: "fb123456789://authorize#access_token=hastoberedacted")!, options: [:]) - - - let event = testMiddleware.lastContext?.payload as? PHGCapturePayload - expect(event?.event) == "Deep Link Opened" - expect(event?.properties?["url"] as? String) == "fb123456789://authorize#access_token=((redacted/fb-auth-token))" - } - - it("redacts sensible URLs from deep links capturing using custom filters") { - testMiddleware.swallowEvent = true - posthog.configuration.payloadFilters["(myapp://auth\\?token=)([^&]+)"] = "$1((redacted/my-auth))" - posthog.configuration.captureDeepLinks = true - posthog.open(URL(string: "myapp://auth?token=hastoberedacted&other=stuff")!, options: [:]) - - - let event = testMiddleware.lastContext?.payload as? PHGCapturePayload - expect(event?.event) == "Deep Link Opened" - expect(event?.properties?["url"] as? String) == "myapp://auth?token=((redacted/my-auth))&other=stuff" - } - - it("defaults PHGQueue to an empty array when missing from file storage") { - let integration = posthog.test_payloadManager()?.test_postHogIntegration() - expect(integration).notTo(beNil()) - integration?.test_fileStorage()?.resetAll() - expect(integration?.test_queue()).to(beEmpty()) - } - } -} diff --git a/PostHogTests/PostHogUtilTests.swift b/PostHogTests/PostHogUtilTests.swift deleted file mode 100644 index 8ea146a89..000000000 --- a/PostHogTests/PostHogUtilTests.swift +++ /dev/null @@ -1,111 +0,0 @@ -import Quick -import Nimble -import PostHog - -class PostHogUtilTests: QuickSpec { - override func spec() { - - it("format NSDate objects to RFC 3339 complaint string") { - let date = Date(timeIntervalSince1970: 0) - let formattedString = createISO8601FormattedString(date) - expect(formattedString) == "1970-01-01T00:00:00.000Z" - - var components = DateComponents() - components.year = 1992 - components.month = 8 - components.day = 6 - components.hour = 7 - components.minute = 32 - components.second = 4 - components.nanosecond = 335000000 - let calendar = NSCalendar(calendarIdentifier: .gregorian)! - calendar.timeZone = TimeZone(secondsFromGMT: -4 * 60 * 60)! - let date2 = calendar.date(from: components)! - let formattedString2 = createISO8601FormattedString(date2) - expect(formattedString2) == "1992-08-06T11:32:04.335Z" - } - - describe("trimQueueItems", { - it("does nothing when count < max") { - let queue = NSMutableArray(array: []) - for i in 1...4 { - queue.add(i) - } - trimQueueItems(queue, 5) - expect(queue) == [1, 2, 3, 4] - } - - it("trims when count > max") { - let queue = NSMutableArray(array: []) - for i in 1...10 { - queue.add(i) - } - trimQueueItems(queue, 5) - expect(queue) == [6, 7, 8, 9, 10] - } - - it("does not trim when count == max") { - let queue = NSMutableArray(array: []) - for i in 1...5 { - queue.add(i) - } - trimQueueItems(queue, 5) - expect(queue) == [1, 2, 3, 4, 5] - } - }) - - describe("JSON traverse", { - let filters = [ - "(foo)": "$1-bar" - ] - - func equals(a: Any, b: Any) -> Bool { - let aData = try! JSONSerialization.data(withJSONObject: a, options: .prettyPrinted) as NSData - let bData = try! JSONSerialization.data(withJSONObject: b, options: .prettyPrinted) - - return aData.isEqual(to: bData) - } - - it("works with strings") { - expect(PHGUtils.traverseJSON("a b foo c", andReplaceWithFilters: filters) as? String) == "a b foo-bar c" - } - - it("works recursively") { - expect(PHGUtils.traverseJSON("a b foo foo c", andReplaceWithFilters: filters) as? String) == "a b foo-bar foo-bar c" - } - - it("works with nested dictionaries") { - let data = [ - "foo": [1, nil, "qfoob", ["baz": "foo"]], - "bar": "foo" - ] as [String: Any] - - guard let input = PHGUtils.traverseJSON(data, andReplaceWithFilters: filters) as? [String: Any] else { - XCTFail("Failed to create actual result from traversed JSON replace") - return - } - - let output = [ - "foo": [1, nil, "qfoo-barb", ["baz": "foo-bar"]], - "bar": "foo-bar" - ] as [String: Any] - - expect(NSDictionary(dictionary: output).isEqual(to: input)) == true - } - - it("works with nested arrays") { - let data = [ - [1, nil, "qfoob", ["baz": "foo"]], - "foo" - ] as [Any] - let input = PHGUtils.traverseJSON(data, andReplaceWithFilters: filters) - let output = [ - [1, nil, "qfoo-barb", ["baz": "foo-bar"]], - "foo-bar" - ] as [Any] - - expect(equals(a: input!, b: output)) == true - } - }) - } -} diff --git a/PostHogTests/SerializationTests.m b/PostHogTests/SerializationTests.m deleted file mode 100644 index 77f8158bc..000000000 --- a/PostHogTests/SerializationTests.m +++ /dev/null @@ -1,47 +0,0 @@ -#import -@import PostHog; - -@protocol PHGSerializableDeepCopy --(id _Nullable) serializableDeepCopy; -@end - -@interface NSDictionary(SerializableDeepCopy) -@end - -@interface NSArray(SerializableDeepCopy) -@end - -@interface SerializationTests : XCTestCase - -@end - -@implementation SerializationTests - -- (void)setUp { - // Put setup code here. This method is called before the invocation of each test method in the class. -} - -- (void)tearDown { - // Put teardown code here. This method is called after the invocation of each test method in the class. -} - -- (void)testDeepCopyAndConformance { - NSDictionary *nonserializable = @{@"test": @1, @"nonserializable": self, @"nested": @{@"nonserializable": self}, @"array": @[@1, @2, @3, self]}; - NSDictionary *serializable = @{@"test": @1, @"nonserializable": @0, @"nested": @{@"nonserializable": @0}, @"array": @[@1, @2, @3, @0]}; - - NSDictionary *aCopy = [serializable serializableDeepCopy]; - XCTAssert(aCopy != serializable); - - NSDictionary *sub = [serializable objectForKey:@"nested"]; - NSDictionary *subCopy = [aCopy objectForKey:@"nested"]; - XCTAssert(sub != subCopy); - - NSDictionary *array = [serializable objectForKey:@"array"]; - NSDictionary *arrayCopy = [aCopy objectForKey:@"array"]; - XCTAssert(array != arrayCopy); - - XCTAssertNoThrow([serializable serializableDeepCopy]); - XCTAssertThrows([nonserializable serializableDeepCopy]); -} - -@end diff --git a/PostHogTests/StoreKitCapturerTests.swift b/PostHogTests/StoreKitCapturerTests.swift deleted file mode 100644 index 4ab5ae643..000000000 --- a/PostHogTests/StoreKitCapturerTests.swift +++ /dev/null @@ -1,63 +0,0 @@ -import Quick -import Nimble -import PostHog - -class mockTransaction: SKPaymentTransaction { - override var transactionIdentifier: String? { - return "tid" - } - override var transactionState: SKPaymentTransactionState { - return SKPaymentTransactionState.purchased - } - override var payment: SKPayment { - return mockPayment() - } -} - -class mockPayment: SKPayment { - override var productIdentifier: String { return "pid" } -} - -class mockProduct: SKProduct { - override var productIdentifier: String { return "pid" } - override var price: NSDecimalNumber { return 3 } - override var localizedTitle: String { return "lt" } - -} - -class mockProductResponse: SKProductsResponse { - override var products: [SKProduct] { - return [mockProduct()] - } -} - -class StoreKitCapturerTests: QuickSpec { - override func spec() { - - var test: TestMiddleware! - var capturer: PHGStoreKitCapturer! - var posthog: PHGPostHog! - - beforeEach { - let config = PHGPostHogConfiguration(apiKey: "foobar") - test = TestMiddleware() - config.middlewares = [test] - posthog = PHGPostHog(configuration: config) - capturer = PHGStoreKitCapturer.captureTransactions(for: posthog) - } - - it("SKPaymentQueue Observer") { - let transaction = mockTransaction() - expect(transaction.transactionIdentifier) == "tid" - capturer.paymentQueue(SKPaymentQueue(), updatedTransactions: [transaction]) - - capturer.productsRequest(SKProductsRequest(), didReceive: mockProductResponse()) - - let payload = test.lastContext?.payload as? PHGCapturePayload - - expect(payload?.event) == "Order Completed" - } - - } - -} diff --git a/PostHogTests/TestUtils/MockPostHogServer.swift b/PostHogTests/TestUtils/MockPostHogServer.swift new file mode 100644 index 000000000..b2be267a0 --- /dev/null +++ b/PostHogTests/TestUtils/MockPostHogServer.swift @@ -0,0 +1,137 @@ +// +// MockPostHogServer.swift +// PostHogTests +// +// Created by Ben White on 21.03.23. +// + +import Foundation +import XCTest + +import OHHTTPStubs +import OHHTTPStubsSwift + +@testable import PostHog + +class MockPostHogServer { + var batchRequests = [URLRequest]() + var batchExpectation: XCTestExpectation? + var decideExpectation: XCTestExpectation? + var batchExpectationCount: Int? + var decideRequests = [URLRequest]() + + func trackBatchRequest(_ request: URLRequest) { + batchRequests.append(request) + + if batchRequests.count >= (batchExpectationCount ?? 0) { + batchExpectation?.fulfill() + } + } + + func trackDecide(_ request: URLRequest) { + decideRequests.append(request) + + decideExpectation?.fulfill() + } + + public var errorsWhileComputingFlags = false + public var return500 = false + + init(port _: Int = 9001) { + stub(condition: isPath("/decide")) { _ in + var flags = [ + "bool-value": true, + "string-value": "test", + "disabled-flag": false, + "number-value": true, + ] + + if self.errorsWhileComputingFlags { + flags["new-flag"] = true + flags.removeValue(forKey: "bool-value") + } + + let obj: [String: Any] = [ + "featureFlags": flags, + "featureFlagPayloads": [ + "payload-bool": "true", + "number-value": "2", + "payload-string": "\"string-value\"", + "payload-json": "{ \"foo\": \"bar\" }", + ], + "errorsWhileComputingFlags": self.errorsWhileComputingFlags, + ] + + return HTTPStubsResponse(jsonObject: obj, statusCode: 200, headers: nil) + } + + stub(condition: isPath("/batch")) { _ in + if self.return500 { + HTTPStubsResponse(jsonObject: [], statusCode: 500, headers: nil) + } else { + HTTPStubsResponse(jsonObject: ["status": "ok"], statusCode: 200, headers: nil) + } + } + + HTTPStubs.onStubActivation { request, _, _ in + if request.url?.path == "/batch" { + self.trackBatchRequest(request) + } else if request.url?.path == "/decide" { + self.trackDecide(request) + } + } + } + + func start(batchCount: Int = 1) { + reset(batchCount: batchCount) + + HTTPStubs.setEnabled(true) + } + + func stop() { + reset() + + HTTPStubs.removeAllStubs() + } + + func reset(batchCount: Int = 1) { + batchRequests = [] + decideRequests = [] + batchExpectation = XCTestExpectation(description: "\(batchCount) batch requests to occur") + decideExpectation = XCTestExpectation(description: "1 decide requests to occur") + batchExpectationCount = batchCount + errorsWhileComputingFlags = false + return500 = false + } + + func parseRequest(_ context: URLRequest, gzip: Bool = true) -> [String: Any]? { + var unzippedData: Data? + do { + if gzip { + unzippedData = try context.body()!.gunzipped() + } else { + unzippedData = context.body()! + } + } catch { + // its ok + } + + return try? JSONSerialization.jsonObject(with: unzippedData!, options: []) as? [String: Any] + } + + func parsePostHogEvents(_ context: URLRequest) -> [PostHogEvent] { + let data = parseRequest(context) + guard let batchEvents = data?["batch"] as? [[String: Any]] else { + return [] + } + + var events = [PostHogEvent]() + + for event in batchEvents { + guard let posthogEvent = PostHogEvent.fromJSON(event) else { continue } + events.append(posthogEvent) + } + + return events + } +} diff --git a/PostHogTests/TestUtils/TestPostHog.swift b/PostHogTests/TestUtils/TestPostHog.swift new file mode 100644 index 000000000..ee6107c4a --- /dev/null +++ b/PostHogTests/TestUtils/TestPostHog.swift @@ -0,0 +1,46 @@ +// +// TestPostHog.swift +// PostHogTests +// +// Created by Ben White on 22.03.23. +// + +import Foundation +import PostHog +import XCTest + +func getBatchedEvents(_ server: MockPostHogServer) -> [PostHogEvent] { + let result = XCTWaiter.wait(for: [server.batchExpectation!], timeout: 15.0) + + if result != XCTWaiter.Result.completed { + XCTFail("The expected requests never arrived") + } + + var events: [PostHogEvent] = [] + for request in server.batchRequests.reversed() { + let items = server.parsePostHogEvents(request) + events.append(contentsOf: items) + } + + return events +} + +func waitDecideRequest(_ server: MockPostHogServer) { + let result = XCTWaiter.wait(for: [server.decideExpectation!], timeout: 15) + + if result != XCTWaiter.Result.completed { + XCTFail("The expected requests never arrived") + } +} + +func getDecideRequest(_ server: MockPostHogServer) -> [[String: Any]] { + waitDecideRequest(server) + + var requests: [[String: Any]] = [] + for request in server.decideRequests.reversed() { + let item = server.parseRequest(request, gzip: false) + requests.append(item!) + } + + return requests +} diff --git a/PostHogTests/TestUtils/URLSession+body.swift b/PostHogTests/TestUtils/URLSession+body.swift new file mode 100644 index 000000000..45696dc18 --- /dev/null +++ b/PostHogTests/TestUtils/URLSession+body.swift @@ -0,0 +1,38 @@ +// +// URLSession+body.swift +// PostHogTests +// +// Created by Ben White on 10.04.23. +// + +import Foundation + +extension URLRequest { + func body() -> Data? { + if httpBody != nil { + return httpBody + } + + guard let bodyStream = httpBodyStream else { return nil } + + bodyStream.open() + + // Will read 16 chars per iteration. Can use bigger buffer if needed + let bufferSize = 16 + + let buffer = UnsafeMutablePointer.allocate(capacity: bufferSize) + + var dat = Data() + + while bodyStream.hasBytesAvailable { + let readDat = bodyStream.read(buffer, maxLength: bufferSize) + dat.append(buffer, count: readDat) + } + + buffer.deallocate() + + bodyStream.close() + + return dat + } +} diff --git a/PostHogTests/UserDefaultsStorageTest.swift b/PostHogTests/UserDefaultsStorageTest.swift deleted file mode 100644 index 4514c191d..000000000 --- a/PostHogTests/UserDefaultsStorageTest.swift +++ /dev/null @@ -1,96 +0,0 @@ -import Quick -import Nimble -import PostHog - -class UserDefaultsStorageTest : QuickSpec { - override func spec() { - var storage : PHGUserDefaultsStorage! - beforeEach { -// let crypto = PHGAES256Crypto(password: "thetrees") -// storage = PHGUserDefaultsStorage(defaults: NSUserDefaults.standardUserDefaults(), namespacePrefix: "posthog", crypto: crypto) -// storage = PHGUserDefaultsStorage(defaults: NSUserDefaults.standardUserDefaults(), namespacePrefix: nil, crypto: crypto) - storage = PHGUserDefaultsStorage(defaults: UserDefaults.standard, namespacePrefix: nil, crypto: nil) - } - - it("persists and loads data") { - let dataIn = "posthog".data(using: String.Encoding.utf8)! - storage.setData(dataIn, forKey: "mydata") - - let dataOut = storage.data(forKey: "mydata") - expect(dataOut) == dataIn - - let strOut = String(data: dataOut!, encoding: .utf8) - expect(strOut) == "posthog" - } - - it("persists and loads string") { - let str = "san francisco" - storage.setString(str, forKey: "city") - expect(storage.string(forKey: "city")) == str - - storage.removeKey("city") - expect(storage.string(forKey: "city")).to(beNil()) - } - - it("persists and loads array") { - let array = [ - "san francisco", - "new york", - "tallinn", - ] - storage.setArray(array, forKey: "cities") - expect(storage.array(forKey: "cities") as? Array) == array - - storage.removeKey("cities") - expect(storage.array(forKey: "cities")).to(beNil()) - } - - it("persists and loads dictionary") { - let dict = [ - "san francisco": "tech", - "new york": "finance", - "paris": "fashion", - ] - storage.setDictionary(dict, forKey: "cityMap") - expect(storage.dictionary(forKey: "cityMap") as? Dictionary) == dict - - storage.removeKey("cityMap") - expect(storage.dictionary(forKey: "cityMap")).to(beNil()) - } - - it("should work with crypto") { - let crypto = PHGAES256Crypto(password: "thetrees") - let s = PHGUserDefaultsStorage(defaults: UserDefaults.standard, namespacePrefix: nil, crypto: crypto) - let dict = [ - "san francisco": "tech", - "new york": "finance", - "paris": "fashion", - ] - s.setDictionary(dict, forKey: "cityMap") - expect(s.dictionary(forKey: "cityMap") as? Dictionary) == dict - - s.removeKey("cityMap") - expect(s.dictionary(forKey: "cityMap")).to(beNil()) - } - - - it("should work with namespace") { - let crypto = PHGAES256Crypto(password: "thetrees") - let s = PHGUserDefaultsStorage(defaults: UserDefaults.standard, namespacePrefix: "posthog", crypto: crypto) - let dict = [ - "san francisco": "tech", - "new york": "finance", - "paris": "fashion", - ] - s.setDictionary(dict, forKey: "cityMap") - expect(s.dictionary(forKey: "cityMap") as? Dictionary) == dict - - s.removeKey("cityMap") - expect(s.dictionary(forKey: "cityMap")).to(beNil()) - } - - afterEach { - storage.resetAll() - } - } -} diff --git a/PostHogTests/Utils/NSData+PHGGUNZIPP.h b/PostHogTests/Utils/NSData+PHGGUNZIPP.h deleted file mode 100644 index 912774446..000000000 --- a/PostHogTests/Utils/NSData+PHGGUNZIPP.h +++ /dev/null @@ -1,10 +0,0 @@ -// https://github.com/nicklockwood/GZIP/blob/master/GZIP/NSData%2BGZIP.m - -#import - - -@interface NSData (PHGGUNZIPP) - -- (NSData *_Nullable)phg_gunzippedData; - -@end diff --git a/PostHogTests/Utils/NSData+PHGGUNZIPP.m b/PostHogTests/Utils/NSData+PHGGUNZIPP.m deleted file mode 100644 index 2ff67be01..000000000 --- a/PostHogTests/Utils/NSData+PHGGUNZIPP.m +++ /dev/null @@ -1,54 +0,0 @@ -// https://github.com/nicklockwood/GZIP/blob/master/GZIP/NSData%2BGZIP.m - -#import -#import -#import "NSData+PHGGZIP.h" -#import "NSData+PHGGUNZIPP.h" - - -@implementation NSData (PHGGUNZIPP) - -- (NSData *)phg_gunzippedData -{ - if (self.length == 0 || ![self phg_isGzippedData]) { - return self; - } - - void *libz = phg_libzOpen(); - int (*inflateInit2_)(z_streamp, int, const char *, int) = - (int (*)(z_streamp, int, const char *, int))dlsym(libz, "inflateInit2_"); - int (*inflate)(z_streamp, int) = (int (*)(z_streamp, int))dlsym(libz, "inflate"); - int (*inflateEnd)(z_streamp) = (int (*)(z_streamp))dlsym(libz, "inflateEnd"); - - z_stream stream; - stream.zalloc = Z_NULL; - stream.zfree = Z_NULL; - stream.avail_in = (uint)self.length; - stream.next_in = (Bytef *)self.bytes; - stream.total_out = 0; - stream.avail_out = 0; - - NSMutableData *output = nil; - if (inflateInit2(&stream, 47) == Z_OK) { - int status = Z_OK; - output = [NSMutableData dataWithCapacity:self.length * 2]; - while (status == Z_OK) { - if (stream.total_out >= output.length) { - output.length += self.length / 2; - } - stream.next_out = (uint8_t *)output.mutableBytes + stream.total_out; - stream.avail_out = (uInt)(output.length - stream.total_out); - status = inflate(&stream, Z_SYNC_FLUSH); - } - if (inflateEnd(&stream) == Z_OK) { - if (status == Z_STREAM_END) { - output.length = stream.total_out; - } - } - } - - return output; -} - - -@end diff --git a/PostHogTests/Utils/TestUtils.swift b/PostHogTests/Utils/TestUtils.swift deleted file mode 100644 index 7343aaf44..000000000 --- a/PostHogTests/Utils/TestUtils.swift +++ /dev/null @@ -1,158 +0,0 @@ -@testable import Nimble -import Nocilla -import PostHog - -class PHGPassthroughMiddleware: PHGMiddleware { - var lastContext: PHGContext? -// Catch more than just the latest context - var allContexts = [PHGContext]() - - func context(_ context: PHGContext, next: @escaping PHGMiddlewareNext) { - allContexts.append(context) - lastContext = context; - next(context) - } -} - -class TestMiddleware: PHGMiddleware { - var lastContext: PHGContext? - var swallowEvent = false - func context(_ context: PHGContext, next: @escaping PHGMiddlewareNext) { - lastContext = context - if !swallowEvent { - next(context) - } - } -} - -extension PHGPostHog { - func test_payloadManager() -> PHGPayloadManager? { - return self.value(forKey: "payloadManager") as? PHGPayloadManager - } -} - -extension PHGPayloadManager { - func test_postHogIntegration() -> PHGPostHogIntegration? { - return self.value(forKey: "integration") as? PHGPostHogIntegration - } -} - -extension PHGPostHogIntegration { - func test_fileStorage() -> PHGFileStorage? { - return self.value(forKey: "fileStorage") as? PHGFileStorage - } - func test_referrer() -> String? { - return self.value(forKey: "referrer") as? String - } - func test_distinctId() -> String? { - return self.value(forKey: "distinctId") as? String - } - func test_flushTimer() -> Timer? { - return self.value(forKey: "flushTimer") as? Timer - } - func test_batchRequest() -> URLSessionUploadTask? { - return self.value(forKey: "batchRequest") as? URLSessionUploadTask - } - func test_queue() -> [AnyObject]? { - return self.value(forKey: "queue") as? [AnyObject] - } - func test_dispatchBackground(block: @escaping @convention(block) () -> Void) { - self.perform(Selector(("dispatchBackground:")), with: block) - } -} - - -class JsonGzippedBody : LSMatcher, LSMatcheable { - - let expectedJson: AnyObject - - init(_ json: AnyObject) { - self.expectedJson = json - } - - func matchesJson(_ json: AnyObject) -> Bool { - let actualValue : () -> NSObject = { - return json as! NSObject - } - let failureMessage = FailureMessage() - let location = SourceLocation() - let matches = Nimble.equal(expectedJson).matches(actualValue, failureMessage: failureMessage, location: location) -// print("matches=\(matches) expected \(expectedJson) actual \(json)") - return matches - } - - override func matches(_ string: String!) -> Bool { - if let data = string.data(using: String.Encoding.utf8), - let json = try? JSONSerialization.jsonObject(with: data, options: []) { - return matchesJson(json as AnyObject) - } - return false - } - - override func matchesData(_ data: Data!) -> Bool { - if let data = (data as NSData).phg_gunzipped(), - let json = try? JSONSerialization.jsonObject(with: data, options: []) { - return matchesJson(json as AnyObject) - } - return false - } - - func matcher() -> LSMatcher! { - return self - } - - func expectedHeaders() -> [String:String] { - let data = try? JSONSerialization.data(withJSONObject: expectedJson, options: []) - let contentLength = (data as NSData?)?.phg_gzipped()?.count ?? 0 - return [ - "Content-Encoding": "gzip", - "Content-Type": "application/json", - "Content-Length": "\(contentLength)", - // Accept-Encoding technically doesn't belong here because - // sending json body doesn't necessarily mean expecting JSON response. But - // there isn't anywhere else that's suitable and we don't exactly want to copy paste this logic many times over - // So will leave it here for now. - "Accept-Encoding": "gzip", - ] - } -} - -typealias AndJsonGzippedBodyMethod = (AnyObject) -> LSStubRequestDSL - -extension LSStubRequestDSL { - var withJsonGzippedBody: AndJsonGzippedBodyMethod { - return { json in - let body = JsonGzippedBody(json) - return self - .withHeaders(body.expectedHeaders())! - .withBody(body)! - } - } -} - -class TestApplication: NSObject, PHGApplicationProtocol { - class BackgroundTask { - let identifier: UInt - var isEnded = false - - init(identifier: UInt) { - self.identifier = identifier - } - } - - var backgroundTasks = [BackgroundTask]() - - // MARK: - PHGApplicationProtocol - var delegate: UIApplicationDelegate? = nil - - func phg_beginBackgroundTask(withName taskName: String?, expirationHandler handler: (() -> Void)? = nil) -> UInt { - let backgroundTask = BackgroundTask(identifier: (backgroundTasks.map({ $0.identifier }).max() ?? 0) + 1) - backgroundTasks.append(backgroundTask) - return backgroundTask.identifier - } - - func phg_endBackgroundTask(_ identifier: UInt) { - guard let index = backgroundTasks.index(where: { $0.identifier == identifier }) else { return } - backgroundTasks[index].isEnded = true - } -} diff --git a/README.md b/README.md index 53a17aedb..8adf9f90b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ -[![Version](https://img.shields.io/cocoapods/v/PostHog.svg?style=flat)](https://cocoapods.org//pods/PostHog) -[![License](https://img.shields.io/cocoapods/l/PostHog.svg?style=flat)](http://cocoapods.org/pods/PostHog) -[![SwiftPM Compatible](https://img.shields.io/badge/SwiftPM-Compatible-F05138.svg)](https://swift.org/package-manager/) +[![Build](https://img.shields.io/github/actions/workflow/status/PostHog/posthog-ios/build.yml?branch=v3.0.0)](https://github.com/PostHog/posthog-ios/actions/workflows/build.yml?query=branch%3Av3.0.0) +[![CocoaPods compadible](https://img.shields.io/cocoapods/v/PostHog.svg)](https://cocoapods.org/pods/PostHog) +[![SwiftPM compatible](https://img.shields.io/badge/spm-compatible-brightgreen.svg?style=flat)](https://swift.org/package-manager) +![platforms](https://img.shields.io/cocoapods/p/PostHog.svg?style=flat) +[![Swift Package Index](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FPostHog%2Fposthog-ios%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/PostHog/posthog-ios) # PostHog iOS @@ -8,18 +10,6 @@ Please see the main [PostHog docs](https://posthog.com/docs). Specifically, the [iOS integration](https://posthog.com/docs/integrations/ios-integration) details. -# Development Guide - -To get started - -1. Install XCode -2. Install [CocoaPods](https://guides.cocoapods.org/using/getting-started.html) -3. Run `pod install` - 1. If you face segmentation faults on M1 Macs, [this might be a potential cause](https://github.com/ffi/ffi/issues/864) - 2. To fix, run `gem install --user-install ffi -- --enable-libffi-alloc` -4. Open the **file** `PostHog.xcworkspace` workspace in XCode -5. Run tests [using the test navigator](https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/testing_with_xcode/chapters/05-running_tests.html) . Skip TvOS tests by changing the target in the top middle bar from `PostHog` to `PostHogTests`. - ## Questions? ### [Join our Slack community.](https://join.slack.com/t/posthogusers/shared_invite/enQtOTY0MzU5NjAwMDY3LTc2MWQ0OTZlNjhkODk3ZDI3NDVjMDE1YjgxY2I4ZjI4MzJhZmVmNjJkN2NmMGJmMzc2N2U3Yjc3ZjI5NGFlZDQ) diff --git a/RELEASING.md b/RELEASING.md index fddd0ffd1..330968899 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,22 +1,14 @@ -# Releasing - -1. Update the version in `PostHog.podspec`, `PostHog/Info.plist`, and `version` in `PostHog/Classes/PHGPostHog.m` to the next release version. -2. Update the `CHANGELOG.md` for the impending release. -3. `git commit -am "Prepare for release X.Y.Z."` (where X.Y.Z is the new version). -4. `git tag -a X.Y.Z -m "Version X.Y.Z"` (where X.Y.Z is the new version). -5. `git push && git push --tags`. -6. `pod trunk push PostHog.podspec --allow-warnings`. -7. Go to [GH Releases](https://github.com/PostHog/posthog-ios/releases) and create a new release from the tag you just pushed. Copy the changelog entry into the description and publish the release. - -## On Apple Silicon - -Run this first: - -```bash -sudo arch -x86_64 gem install ffi -arch -x86_64 pod install -``` - -Then, [configure XCode CLI tools](https://stackoverflow.com/questions/29108172/how-do-i-fix-the-xcrun-unable-to-find-simctl-error). - -Finally, prepend `arch -x86_64` to the pod command in the steps above. +Releasing +========= + + 1. Update the CHANGELOG.md with the version and date + 2. Go to [GH Releases](https://github.com/PostHog/posthog-ios/releases) + 3. Choose a tag name (e.g. `3.0.0`), this is the version number of the release. + 1. Preview releases follow the pattern `3.0.0-alpha.1`, `3.0.0-beta.1`, `3.0.0-RC.1` + 4. Choose a release name (e.g. `3.0.0`), ideally it matches the above. + 5. Write a description of the release. + 6. Publish the release. + 7. GH Action (release.yml) is doing everything else automatically. + 1. SPM uses the tag name to determine the version, directly from the repo. + 2. CocoaPods are published. + 8. Done. diff --git a/USAGE.md b/USAGE.md new file mode 100644 index 000000000..7903963fa --- /dev/null +++ b/USAGE.md @@ -0,0 +1,199 @@ +# How to use the iOS SDK v3 + +## Setup + +### CocoaPods + +```text +pod "PostHog", "~> 3.0.0" +``` + +### SPM + +```swift +dependencies: [ + .package(url: "https://github.com/PostHog/posthog-ios.git", from: "3.0.0") +], +``` + +## Examples + +```swift +import PostHog + +let config = PostHogConfig(apiKey: apiKey) +PostHogSDK.shared.setup(config) +``` + +Set a custom `host` (Self-Hosted) + +```swift +let config = PostHogConfig(apiKey: apiKey, host: host) +``` + +Change the default configuration + +```swift +let config = PostHogConfig(apiKey: apiKey) +config.captureScreenViews = false +config.captureApplicationLifecycleEvents = false +config.debug = true +// .. and more +``` + +If you don't want to use the global/singleton instance, you can create your own PostHog SDK instance +and hold it + +```swift +let config = PostHogConfig(apiKey: apiKey) +let postHog = PostHogSDK.with(config) + +PostHogSDK.shared.capture("user_signed_up") +``` + +Enable or Disable the SDK to capture events + +```swift +// During SDK setup +let config = PostHogConfig(apiKey: apiKey) +// the SDK is enabled by default +config.optOut = true +PostHogSDK.shared.setup(config) + +// At runtime +PostHogSDK.shared.optOut() + +// Check it and opt-in +if (PostHogSDK.shared.isOptOut()) { + PostHogSDK.shared.optIn() +} +``` + +Capture a screen view event + +```swift +let config = PostHogConfig(apiKey: apiKey) +// it's enabled by default +config.captureScreenViews = true +PostHogSDK.shared.setup(config) + +// Or manually +PostHogSDK.shared.screen("Dashboard", properties: ["url": "...", "background": "blue"]) +``` + +Capture an event + +```swift +PostHogSDK.shared.capture("Dashboard", properties: ["is_free_trial": true]) +// check out the `userProperties`, `userPropertiesSetOnce` and `groupProperties` parameters. +``` + +Identify the user + +```swift +PostHogSDK.shared.identify("user123", userProperties: ["email": "user@PostHogSDK.shared.com"]) +``` + +Create an alias for the current user + +```swift +PostHogSDK.shared.alias("theAlias") +``` + +Identify a group + +```swift +PostHogSDK.shared.group(type: "company", key: "company_id_in_your_db", groupProperties: ["name": "Awesome Inc."]) +``` + +Registering and unregistering a context to be sent for all the following events + +```swift +// Register +PostHogSDK.shared.register(["team_id": 22]) + +// Unregister +PostHogSDK.shared.unregister("team_id") +``` + +Load feature flags automatically + +```swift +// Subscribe to feature flags notification +NotificationCenter.default.addObserver(self, selector: #selector(receiveFeatureFlags), name: PostHogSDK.didReceiveFeatureFlags, object: nil) +PostHogSDK.shared.setup(config) + +The "receiveFeatureFlags" method will be called when the SDK receives the feature flags from the server. + +// And/Or manually +PostHogSDK.shared.reloadFeatureFlags { + if PostHogSDK.shared.isFeatureEnabled("paidUser") { + // do something + } +} +``` + +Read feature flags + +```swift +let paidUser = PostHogSDK.shared.isFeatureEnabled("paidUser") + +// Or +let paidUser = PostHogSDK.shared.getFeatureFlag("paidUser") as? Bool +``` + +Read feature flags variant/payload + +```swift +let premium = PostHogSDK.shared.getFeatureFlagPayload("premium") as? Bool +``` + +Read the current `distinctId` + +```swift +let distinctId = PostHogSDK.shared.getDistinctId() +``` + +Flush the SDK by sending all the pending events right away + +```swift +PostHogSDK.shared.flush() +``` + +Reset the SDK and delete all the cached properties + +```swift +PostHogSDK.shared.reset() +``` + +Close the SDK + +```swift +PostHogSDK.shared.close() +``` + +## Breaking changes + +- `receivedRemoteNotification` has been removed. +- `registeredForRemoteNotificationsWithDeviceToken` has been removed. +- `handleActionWithIdentifier` has been removed. +- `continueUserActivity` has been removed. +- `openURL` has been removed. +- `captureDeepLinks` has been removed. +- `captureInAppPurchases` has been removed. +- `capturePushNotifications` has been removed. +- `shouldUseLocationServices` config has been removed. +- `payloadFilters` config has been removed. +- `shouldUseBluetooth` config has been removed. +- `crypto` config has been removed. +- `middlewares` config has been removed. +- `httpSessionDelegate` config has been removed. +- `requestFactory` config has been removed. +- `shouldSendDeviceID` config has been removed, events won't contain the `$device_id` attribute anymore. +- `launchOptions` config has been removed, the `Application Opened` event won't contain the `referring_application` and `url` attributes anymore. +- `captureScreenViews` is enabled by default (it does not work on SwiftUI) +- `captureApplicationLifecycleEvents` is enabled by default + +For the removed methods, you can use the `PostHogSDK.shared.capture` methods manually instead. + +If any of the breaking changes are blocking you, please [open an issue](https://github.com/PostHog/posthog-ios/issues/new) and let us know your use case. diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index d7cacf98f..000000000 --- a/codecov.yml +++ /dev/null @@ -1,28 +0,0 @@ -# https://github.com/codecov/support/blob/master/codecov.yml -# This codecov.yml is the default configuration for -# all repositories on Codecov. You may adjust the settings -# below in your own codecov.yml in your repository. -# -coverage: - precision: 2 - round: down - range: 70...100 - - status: - # Learn more at https://codecov.io/docs#yaml_default_commit_status - project: - default: - target: auto - threshold: 5 - patch: - default: - enabled: no - changes: false - -comment: - layout: "header, diff" - behavior: default # update if exists else create new - -ignore: - - Pods - - PostHog/Vendor diff --git a/dangerfile.js b/dangerfile.js new file mode 100644 index 000000000..91f473ad2 --- /dev/null +++ b/dangerfile.js @@ -0,0 +1,60 @@ +async function checkChangelog() { + const changelogFile = "CHANGELOG.md"; + + // Check if skipped + const skipChangelog = + danger.github && (danger.github.pr.body + "").includes("#skip-changelog"); + + if (skipChangelog) { + return; + } + + // Check if current PR has an entry in changelog + const changelogContents = await danger.github.utils.fileContents( + changelogFile + ); + + const hasChangelogEntry = RegExp(`#${danger.github.pr.number}\\b`).test( + changelogContents + ); + + if (hasChangelogEntry) { + return; + } + + // Report missing changelog entry + fail( + "Please consider adding a changelog entry for the next release.", + changelogFile + ); + + const prTitleFormatted = danger.github.pr.title + .split(": ") + .slice(-1)[0] + .trim() + .replace(/\.+$/, ""); + + markdown( + ` +### Instructions and example for changelog +Please add an entry to \`CHANGELOG.md\` to the "Next" section. Make sure the entry includes this PR's number. +Example: +\`\`\`markdown +## Next +- ${prTitleFormatted} ([#${danger.github.pr.number}](${danger.github.pr.html_url})) +\`\`\` +If none of the above apply, you can opt out of this check by adding \`#skip-changelog\` to the PR description.`.trim() + ); +} + +async function checkAll() { + const isDraft = danger.github.pr.mergeable_state === "draft"; + + if (isDraft) { + return; + } + + await checkChangelog(); +} + +schedule(checkAll); diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh new file mode 100755 index 000000000..7ef882a81 --- /dev/null +++ b/scripts/bump-version.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# ./scripts/bump-version.sh +# eg ./scripts/bump-version.sh "3.0.0-alpha.1" + +set -eux + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd $SCRIPT_DIR/.. + +NEW_VERSION="$1" + +# Replace `postHogVersion` with the given version +perl -pi -e "s/postHogVersion = \".*\"/postHogVersion = \"$NEW_VERSION\"/" PostHog/PostHogVersion.swift + +# Replace `s.version` with the given version +perl -pi -e "s/s.version = \".*\"/s.version = \"$NEW_VERSION\"/" PostHog.podspec diff --git a/scripts/commit-code.sh b/scripts/commit-code.sh new file mode 100755 index 000000000..28b071dd9 --- /dev/null +++ b/scripts/commit-code.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd $SCRIPT_DIR/.. + +GITHUB_BRANCH="${1}" + +if [[ $(git status) == *"nothing to commit"* ]]; then + echo "Nothing to commit." +else + echo "Going to push the changes." + git config --global user.name 'PostHog Github Bot' + git config --global user.email 'github-bot@posthog.com' + git fetch + git checkout ${GITHUB_BRANCH} + git commit -am "Update version" + git push --set-upstream origin ${GITHUB_BRANCH} +fi