Getting started

Mobile apps: Swift & Flutter

Native SDKs put your app in the same workspace as your website: Portfolio, Users, Lifecycle, revenue and every other page read app traffic the same way. Both are open source (Apache-2.0).

SDKPlatformsInstall
SwiftiOS 14+, iPadOS, macOS 11+Swift Package Manager: https://github.com/Vitrus-Dev/vitrus-swift — zero dependencies
Flutter & DartiOS, Android, desktopvitrus: { git: https://github.com/Vitrus-Dev/vitrus-dart } — depends on crypto and http only

Swift

import Vitrus

Vitrus.shared.configure(site: "SITE_ID")
Vitrus.shared.screen("Invoices")
Vitrus.shared.track("invoice_sent", ["items": 3])
Vitrus.shared.identify(user.id)          // hashed on device
Vitrus.shared.plan("pro")
Vitrus.shared.revenue(4.99, currency: "USD")

Flutter

final dir = await getApplicationSupportDirectory();
await Vitrus.instance.configure(site: 'SITE_ID', storage: FileStorage(dir.path), appVersion: '2.3.0');
Vitrus.instance.screen('Invoices');
Vitrus.instance.identify(user.id);
// in didChangeAppLifecycleState: paused → flush(), resumed → appOpen()

Why the server needed three things for apps

  • A daily device value. A browser visitor is a daily hash of IP and user-agent. On mobile, thousands of phones share one carrier IP and one app user-agent, so that recipe would merge them. The SDKs send a random value regenerated every UTC day, and the server hashes it with the day instead — still no identifier that lasts past midnight.
  • Honest times for offline events. The server's clock is the clock. An event sent later says how long ago it happened (ago, from the monotonic clock, at most a day); the server subtracts it from its own time.
  • Batches. POST /api/batch takes up to 50 events of one site; each goes through the same exclusions, quota and validation as /api/d, and the answer lists any that were refused.

App traffic is bucketed by the SDK's user-agent (Vitrus-iOS/0.1 (iPhone; iOS 18.0)): the browser column reads iOS app or Android app, the device column phone / tablet / desktop, and the app version is the release tag. Screens are page views at /screen/<name>.

Privacy and the stores

  • The raw user id never leaves the device; the same hash as the browser, so one person on web and in the app is one person.
  • Personal properties are dropped on device with the server's full list, and again at ingest.
  • No advertising id, no IDFV or Android id, no fingerprint — no App Tracking Transparency prompt. The Swift package ships PrivacyInfo.xcprivacy; both READMEs list the App Store and Google Play Data safety answers.

Not on mobile

Session replay and heatmaps are web-only: they are built from the DOM, and a native screen has none.