Alcatelz
Philosophy
Projects
Background 1

Projects

Mapr
QRHue
TextClip
Låne Lageret
Vegar Lee Berentsen

Privacy Policy — Mapr

Mapr collects data solely for analytics purposes. The data we collect is used to improve the app, understand usage patterns, and measure performance. Collected analytics data is deleted from our systems — we do not use it for advertising or other profiling purposes.

Exactly what we collect

The following is the exact analytics data collected by Mapr (source code):

//
//  AnalyticsService.swift
//  Mapr
//
//  Created by vegar berentsen on 27/01/2024.
//

import SwiftUI
import CloudKit
import CoreLocation

class AnalyticsService: NSObject, CLLocationManagerDelegate {
    static let shared = AnalyticsService()
    private var hasTrackedInitialTab = false

    // Reference to the specific CloudKit container for analytics
    let container = CKContainer(identifier: "iCloud.MaprAnalytics")
    var publicDatabase: CKDatabase {
        return container.publicCloudDatabase
    }

    private var sessionStart: Date?

    func startSession() {
        sessionStart = Date()
    }

    private let locationManager = CLLocationManager()
    private var currentLocation: CLLocation?

    override init() {
        super.init()
        locationManager.delegate = self
        locationManager.requestWhenInUseAuthorization() // or requestAlwaysAuthorization()
        locationManager.startUpdatingLocation()
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        currentLocation = locations.last
    }
    
    // --- START OF MODIFICATION ---
    // The function now accepts an optional completion handler to signal when the save is complete.
    func endSession(completion: (() -> Void)? = nil) {
        guard let start = sessionStart else {
            completion?()
            return
        }

        let sessionEnd = Date()
        let duration = sessionEnd.timeIntervalSince(start)

        let record = CKRecord(recordType: "AppOpenEvent")
        record["timestamp"] = start
        record["sessionDuration"] = duration

        record["appVersion"] = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown"
        
    #if os(iOS)
        // iOS-specific data
        record["platform"] = "iOS"
        record["deviceId"] = UIDevice.current.identifierForVendor?.uuidString ?? "Unknown"
        record["osVersion"] = UIDevice.current.systemVersion
        record["deviceModel"] = UIDevice.current.model
        
    #elseif os(macOS)
        // macOS-specific data
        record["platform"] = "macOS"
        
        // Get/create a persistent anonymous device ID from UserDefaults
        let userDefaults = UserDefaults.standard
        let deviceIdKey = "analyticsDeviceId"
        let deviceId: String
        if let existingId = userDefaults.string(forKey: deviceIdKey) {
            deviceId = existingId
        } else {
            let newId = UUID().uuidString
            userDefaults.set(newId, forKey: deviceIdKey)
            deviceId = newId
        }
        record["deviceId"] = deviceId
        
        record["osVersion"] = ProcessInfo.processInfo.operatingSystemVersionString
        
        // Get Mac model identifier (e.g., "MacBookPro18,1")
        var size = 0
        sysctlbyname("hw.model", nil, &size, nil, 0)
        var machine = [CChar](repeating: 0, count: size)
        sysctlbyname("hw.model", &machine, &size, nil, 0)
        let modelIdentifier = String(cString: machine)
        record["deviceModel"] = modelIdentifier
    #endif

        record["locale"] = Locale.current.identifier
        if let location = currentLocation {
            record["location"] = CLLocation(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
        }
        if let userIdentifier = SignInWithAppleManager.shared.userIdentifier {
            record["userId"] = userIdentifier
        }

        // Save the record to the public CloudKit database
        publicDatabase.save(record) { record, error in
            if let error = error {
                print("CloudKit save error: \(error)")
            } else {
                print("Successfully saved AppOpenEvent, to the public db")
            }
            // Signal completion regardless of success or error so the app can quit.
            completion?()
        }
    }
    // --- END OF MODIFICATION ---
    
    func trackInitialTabSelection(tabNumber: Int) {
        if !hasTrackedInitialTab {
            trackTabSelection(tabNumber: tabNumber)
            hasTrackedInitialTab = true
        }
    }
    
    func trackTabSelection(tabNumber: Int) {
        let record = CKRecord(recordType: "TabSelectionEvent")
        record["timestamp"] = Date()
        record["tabNumber"] = tabNumber
        record["userId"] = SignInWithAppleManager.shared.userIdentifier ?? "Unknown"

        publicDatabase.save(record) { record, error in
            if let error = error {
                print("CloudKit save error: \(error)")
            } else {
                print("Successfully saved TabSelectionEvent")
            }
        }
    }
    
    func trackDownload() {
        let record = CKRecord(recordType: "DownloadEvent")
        record["timestamp"] = Date()
        record["userId"] = SignInWithAppleManager.shared.userIdentifier ?? "Unknown"
        
        publicDatabase.save(record) { record, error in
            if let error = error {
                print("CloudKit save error: \(error)")
            } else {
                print("Successfully saved DownloadEvent")
            }
        }
    }

    func trackRetentionRates() {
        let record = CKRecord(recordType: "RetentionEvent")
        record["timestamp"] = Date()
        record["userId"] = SignInWithAppleManager.shared.userIdentifier ?? "Unknown"
        
        // Logic to calculate retention rates and update record fields accordingly

        publicDatabase.save(record) { record, error in
            if let error = error {
                print("CloudKit save error: \(error)")
            } else {
                print("Successfully saved RetentionEvent")
            }
        }
    }
    
    func trackActiveUser() {
        let record = CKRecord(recordType: "ActiveUserEvent")
        record["timestamp"] = Date()
        record["userId"] = SignInWithAppleManager.shared.userIdentifier ?? "Unknown"
        
        publicDatabase.save(record) { record, error in
            if let error = error {
                print("CloudKit save error: \(error)
")
            } else {
                print("Successfully saved ActiveUserEvent")
            }
        }
    }
    
    func trackUserBehavior(eventType: String, details: [String: Any]) {
        let record = CKRecord(recordType: "UserBehaviorEvent")
        record["timestamp"] = Date()
        record["eventType"] = eventType
        record["userId"] = SignInWithAppleManager.shared.userIdentifier ?? "Unknown"
        
        for (key, value) in details {
            record[key] = value as? CKRecordValue
        }

        publicDatabase.save(record) { record, error in
            if let error = error {
                print("CloudKit save error: \(error)")
            } else {
                print("Successfully saved UserBehaviorEvent")
            }
        }
    }
    
    // --- MODIFIED FUNCTION ---
    
    /// Tracks when a user prints a document.
    /// This should be called from the ViewModel responsible for printing.
    func trackPrintEvent(documentName: String, pageCount: Int, phase: String) { // Added phase
        let record = CKRecord(recordType: "PrintEvent")
        record["timestamp"] = Date()
        record["documentName"] = documentName
        record["pageCount"] = pageCount
        record["projectPhase"] = phase // Added phase field
        record["userId"] = SignInWithAppleManager.shared.userIdentifier ?? "Unknown"

        publicDatabase.save(record) { record, error in
            if let error = error {
                print("CloudKit save error (PrintEvent): \(error)")
            } else {
                print("Successfully saved PrintEvent with phase: \(phase)")
            }
        }
    }
    // --- END OF MODIFICATION ---
}

Summary of collected fields

  • timestamp
  • sessionDuration
  • appVersion
  • platform, deviceId (anonymous), osVersion, deviceModel
  • locale
  • location (if location permission granted)
  • userId (from Sign in with Apple, if available)
  • tabNumber, download/retention/active/behavior/print event details

Deletion: collected analytics data is deleted from our systems. If you would like more information or want to request deletion related to your account, please contact us directly from the app support channels.