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).
| SDK | Platforms | Install |
|---|---|---|
| Swift | iOS 14+, iPadOS, macOS 11+ | Swift Package Manager: https://github.com/Vitrus-Dev/vitrus-swift — zero dependencies |
| Flutter & Dart | iOS, Android, desktop | vitrus: { 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/batchtakes 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.