Skip to content

Commit

Permalink
Add an option to override platform context properties (close #865)
Browse files Browse the repository at this point in the history
PR #866
  • Loading branch information
matus-tomlein authored Jan 30, 2024
1 parent 39b8250 commit 20231c6
Show file tree
Hide file tree
Showing 15 changed files with 433 additions and 114 deletions.
5 changes: 5 additions & 0 deletions Sources/Core/InternalQueue/TrackerControllerIQWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ class TrackerControllerIQWrapper: TrackerController {
get { return InternalQueue.sync { controller.platformContextProperties } }
set { InternalQueue.sync { controller.platformContextProperties = newValue } }
}

var platformContextRetriever: PlatformContextRetriever? {
get { return InternalQueue.sync { controller.platformContextRetriever } }
set { InternalQueue.sync { controller.platformContextRetriever = newValue } }
}

var geoLocationContext: Bool {
get { return InternalQueue.sync { controller.geoLocationContext } }
Expand Down
106 changes: 84 additions & 22 deletions Sources/Core/Subject/PlatformContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,27 @@ class PlatformContext {
private var lastUpdatedEphemeralNetworkDict: TimeInterval = 0.0
private var deviceInfoMonitor: DeviceInfoMonitor

/// Overrides for retrieving property values
var platformContextRetriever: PlatformContextRetriever

/// List of properties of the platform context to track
var platformContextProperties: [PlatformContextProperty]?

/// Initializes a newly allocated PlatformContext object with custom update frequency for mobile and network properties and a custom device info monitor
/// - Parameters:
/// - platformContextProperties: List of properties of the platform context to track
/// - platformContextRetriever: Overrides for the property retrieving behavior
/// - mobileDictUpdateFrequency: Minimal gap between subsequent updates of mobile platform information
/// - networkDictUpdateFrequency: Minimal gap between subsequent updates of network platform information
/// - deviceInfoMonitor: Device monitor for fetching platform information
/// - Returns: a PlatformContext object
init(platformContextProperties: [PlatformContextProperty]? = nil,
platformContextRetriever: PlatformContextRetriever? = nil,
mobileDictUpdateFrequency: TimeInterval = 1.0,
networkDictUpdateFrequency: TimeInterval = 10.0,
deviceInfoMonitor: DeviceInfoMonitor = DeviceInfoMonitor()) {
self.platformContextProperties = platformContextProperties
self.platformContextRetriever = platformContextRetriever ?? PlatformContextRetriever()
self.mobileDictUpdateFrequency = mobileDictUpdateFrequency
self.networkDictUpdateFrequency = networkDictUpdateFrequency
self.deviceInfoMonitor = deviceInfoMonitor
Expand All @@ -52,7 +58,7 @@ class PlatformContext {

/// Updates and returns payload dictionary with device context information.
/// - Parameter userAnonymisation: Whether to anonymise user identifiers (IDFA values)
func fetchPlatformDict(userAnonymisation: Bool, advertisingIdentifierRetriever: (() -> UUID?)?) -> Payload {
func fetchPlatformDict(userAnonymisation: Bool) -> Payload {
#if os(iOS) || os(visionOS)
let now = Date().timeIntervalSince1970
if now - lastUpdatedEphemeralMobileDict >= mobileDictUpdateFrequency {
Expand All @@ -69,10 +75,8 @@ class PlatformContext {
copy[kSPMobileAppleIdfv] = nil
return copy
} else {
if let retriever = advertisingIdentifierRetriever {
if shouldTrack(.appleIdfa) && platformDict.dictionary[kSPMobileAppleIdfa] == nil {
platformDict[kSPMobileAppleIdfa] = retriever()?.uuidString
}
if shouldTrack(.appleIdfa) && platformDict.dictionary[kSPMobileAppleIdfa] == nil {
platformDict[kSPMobileAppleIdfa] = platformContextRetriever.appleIdfa?()?.uuidString
}
return platformDict
}
Expand All @@ -82,10 +86,22 @@ class PlatformContext {

func setPlatformDict() {
platformDict = Payload()
platformDict[kSPPlatformOsType] = deviceInfoMonitor.osType
platformDict[kSPPlatformOsVersion] = deviceInfoMonitor.osVersion
platformDict[kSPPlatformDeviceManu] = deviceInfoMonitor.deviceVendor
platformDict[kSPPlatformDeviceModel] = deviceInfoMonitor.deviceModel
platformDict[kSPPlatformOsType] = (
platformContextRetriever.osType == nil ?
deviceInfoMonitor.osType : platformContextRetriever.osType?()
)
platformDict[kSPPlatformOsVersion] = (
platformContextRetriever.osVersion == nil ?
deviceInfoMonitor.osVersion : platformContextRetriever.osVersion?()
)
platformDict[kSPPlatformDeviceManu] = (
platformContextRetriever.deviceVendor == nil ?
deviceInfoMonitor.deviceVendor : platformContextRetriever.deviceVendor?()
)
platformDict[kSPPlatformDeviceModel] = (
platformContextRetriever.deviceModel == nil ?
deviceInfoMonitor.deviceModel : platformContextRetriever.deviceModel?()
)

#if os(iOS) || os(visionOS)
setMobileDict()
Expand All @@ -94,20 +110,39 @@ class PlatformContext {

func setMobileDict() {
if shouldTrack(.resolution) {
platformDict[kSPMobileResolution] = deviceInfoMonitor.resolution
platformDict[kSPMobileResolution] = (
platformContextRetriever.resolution == nil ?
deviceInfoMonitor.resolution : platformContextRetriever.resolution?()
)
}
if shouldTrack(.language) {
// the schema has a max-length 8 for language which iOS exceeds sometimes
if let language = deviceInfoMonitor.language { platformDict[kSPMobileLanguage] = String(language.prefix(8)) }
let language = (
platformContextRetriever.language == nil ?
deviceInfoMonitor.language : platformContextRetriever.language?()
)
if let language = language { platformDict[kSPMobileLanguage] = String(language.prefix(8)) }
}
if shouldTrack(.scale) {
platformDict[kSPMobileScale] = deviceInfoMonitor.scale
platformDict[kSPMobileScale] = (
platformContextRetriever.scale == nil ?
deviceInfoMonitor.scale : platformContextRetriever.scale?()
)
}
if shouldTrack(.carrier) {
platformDict[kSPMobileCarrier] = deviceInfoMonitor.carrierName
platformDict[kSPMobileCarrier] = (
platformContextRetriever.carrier == nil ?
deviceInfoMonitor.carrierName : platformContextRetriever.carrier?()
)
}
if shouldTrack(.totalStorage) {
platformDict[kSPMobileTotalStorage] = platformContextRetriever.totalStorage?()
}
if shouldTrack(.physicalMemory) {
platformDict[kSPMobilePhysicalMemory] = deviceInfoMonitor.physicalMemory
platformDict[kSPMobilePhysicalMemory] = (
platformContextRetriever.physicalMemory == nil ?
deviceInfoMonitor.physicalMemory : platformContextRetriever.physicalMemory?()
)
}

setEphemeralMobileDict()
Expand All @@ -118,34 +153,61 @@ class PlatformContext {
lastUpdatedEphemeralMobileDict = Date().timeIntervalSince1970

if shouldTrack(.appleIdfv) && platformDict[kSPMobileAppleIdfv] == nil {
platformDict[kSPMobileAppleIdfv] = deviceInfoMonitor.appleIdfv
platformDict[kSPMobileAppleIdfv] = (
platformContextRetriever.appleIdfv == nil ?
deviceInfoMonitor.appleIdfv : platformContextRetriever.appleIdfv?()
)
}

if shouldTrack(.batteryLevel) {
platformDict[kSPMobileBatteryLevel] = deviceInfoMonitor.batteryLevel
platformDict[kSPMobileBatteryLevel] = (
platformContextRetriever.batteryLevel == nil ?
deviceInfoMonitor.batteryLevel : platformContextRetriever.batteryLevel?()
)
}
if shouldTrack(.batteryState) {
platformDict[kSPMobileBatteryState] = deviceInfoMonitor.batteryState
platformDict[kSPMobileBatteryState] = (
platformContextRetriever.batteryState == nil ?
deviceInfoMonitor.batteryState : platformContextRetriever.batteryState?()
)
}
if shouldTrack(.lowPowerMode) {
platformDict[kSPMobileLowPowerMode] = deviceInfoMonitor.isLowPowerModeEnabled
platformDict[kSPMobileLowPowerMode] = (
platformContextRetriever.lowPowerMode == nil ?
deviceInfoMonitor.isLowPowerModeEnabled : platformContextRetriever.lowPowerMode?()
)
}
if shouldTrack(.availableStorage) {
platformDict[kSPMobileAvailableStorage] = platformContextRetriever.availableStorage?()
}
if shouldTrack(.appAvailableMemory) {
platformDict[kSPMobileAppAvailableMemory] = deviceInfoMonitor.appAvailableMemory
platformDict[kSPMobileAppAvailableMemory] = (
platformContextRetriever.appAvailableMemory == nil ?
deviceInfoMonitor.appAvailableMemory : platformContextRetriever.appAvailableMemory?()
)
}
if shouldTrack(.isPortrait) {
platformDict[kSPMobileIsPortrait] = deviceInfoMonitor.isPortrait
platformDict[kSPMobileIsPortrait] = (
platformContextRetriever.isPortrait == nil ?
deviceInfoMonitor.isPortrait : platformContextRetriever.isPortrait?()
)
}
}

func setEphemeralNetworkDict() {
lastUpdatedEphemeralNetworkDict = Date().timeIntervalSince1970

if shouldTrack(.networkTechnology) {
platformDict[kSPMobileNetworkTech] = deviceInfoMonitor.networkTechnology
platformDict[kSPMobileNetworkTech] = (
platformContextRetriever.networkTechnology == nil ?
deviceInfoMonitor.networkTechnology : platformContextRetriever.networkTechnology?()
)
}
if shouldTrack(.networkType) {
platformDict[kSPMobileNetworkType] = deviceInfoMonitor.networkType
platformDict[kSPMobileNetworkType] = (
platformContextRetriever.networkType == nil ?
deviceInfoMonitor.networkType : platformContextRetriever.networkType?()
)
}
}

Expand Down
29 changes: 22 additions & 7 deletions Sources/Core/Subject/Subject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,30 @@ import Foundation
/// This class is used to access and persist user information, it represents the current user being tracked.
class Subject : NSObject {
private var standardDict: [String : String] = [:]
private var platformContextManager: PlatformContext
private var geoDict: [String : NSObject] = [:]

var geoLocationContext = false

// MARK: - Platform context

private var platformContextManager: PlatformContext

var platformContext = false

var platformContextProperties: [PlatformContextProperty]? {
get { return platformContextManager.platformContextProperties }
set { platformContextManager.platformContextProperties = newValue }
}

var platformContextRetriever: PlatformContextRetriever {
get { return platformContextManager.platformContextRetriever }
set { platformContextManager.platformContextRetriever = newValue }
}

var geoLocationContext = false
var advertisingIdentifierRetriever: (() -> UUID?)? {
get { return platformContextManager.platformContextRetriever.appleIdfa }
set { platformContextManager.platformContextRetriever.appleIdfa = newValue }
}

// MARK: - Standard Dictionary

Expand Down Expand Up @@ -194,9 +207,13 @@ class Subject : NSObject {

init(platformContext: Bool = false,
platformContextProperties: [PlatformContextProperty]? = nil,
platformContextRetriever: PlatformContextRetriever? = nil,
geoLocationContext geoContext: Bool = false,
subjectConfiguration config: SubjectConfiguration? = nil) {
self.platformContextManager = PlatformContext(platformContextProperties: platformContextProperties)
self.platformContextManager = PlatformContext(
platformContextProperties: platformContextProperties,
platformContextRetriever: platformContextRetriever
)
super.init()
platformContextManager.platformContextProperties = platformContextProperties
self.platformContext = platformContext
Expand Down Expand Up @@ -252,11 +269,9 @@ class Subject : NSObject {
/// Gets all platform dictionary pairs to decorate event with. Returns nil if not enabled.
/// - Parameter userAnonymisation: Whether to anonymise user identifiers
/// - Returns: A SPPayload with all platform specific pairs.
func platformDict(userAnonymisation: Bool, advertisingIdentifierRetriever: (() -> UUID?)?) -> Payload? {
func platformDict(userAnonymisation: Bool) -> Payload? {
if platformContext {
return platformContextManager.fetchPlatformDict(
userAnonymisation: userAnonymisation,
advertisingIdentifierRetriever: advertisingIdentifierRetriever)
return platformContextManager.fetchPlatformDict(userAnonymisation: userAnonymisation)
} else {
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Core/Tracker/ServiceProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ class ServiceProvider: NSObject, ServiceProviderProtocol {
return Subject(
platformContext: trackerConfiguration.platformContext,
platformContextProperties: trackerConfiguration.platformContextProperties,
platformContextRetriever: trackerConfiguration.platformContextRetriever,
geoLocationContext: trackerConfiguration.geoLocationContext,
subjectConfiguration: subjectConfiguration)
}
Expand Down Expand Up @@ -304,7 +305,6 @@ class ServiceProvider: NSObject, ServiceProviderProtocol {
tracker.trackerDiagnostic = trackerConfiguration.diagnosticAutotracking
tracker.userAnonymisation = trackerConfiguration.userAnonymisation
tracker.immersiveSpaceContext = trackerConfiguration.immersiveSpaceContext
tracker.advertisingIdentifierRetriever = trackerConfiguration.advertisingIdentifierRetriever
if gdprConfiguration.sourceConfig != nil {
tracker.gdprContext = GDPRContext(
basis: gdprConfiguration.basisForProcessing,
Expand Down
6 changes: 1 addition & 5 deletions Sources/Core/Tracker/Tracker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,6 @@ class Tracker: NSObject {
var isTracking: Bool {
return dataCollection
}

var advertisingIdentifierRetriever: (() -> UUID?)?

init(trackerNamespace: String,
appId: String?,
Expand Down Expand Up @@ -558,9 +556,7 @@ class Tracker: NSObject {

func addBasicContexts(event: TrackerEvent) {
if subject != nil {
if let platformDict = subject?.platformDict(
userAnonymisation: userAnonymisation,
advertisingIdentifierRetriever: advertisingIdentifierRetriever)?.dictionary {
if let platformDict = subject?.platformDict(userAnonymisation: userAnonymisation)?.dictionary {
event.addContextEntity(SelfDescribingJson(schema: platformContextSchema, andDictionary: platformDict))
}
if let geoLocationDict = subject?.geoLocationDict {
Expand Down
9 changes: 7 additions & 2 deletions Sources/Core/Tracker/TrackerControllerImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ class TrackerControllerImpl: Controller, TrackerController {
tracker.subject?.platformContextProperties = newValue
}
}

var platformContextRetriever: PlatformContextRetriever? {
get { return tracker.subject?.platformContextRetriever }
set { if let retriever = newValue { tracker.subject?.platformContextRetriever = retriever } }
}

var geoLocationContext: Bool {
get {
Expand Down Expand Up @@ -294,11 +299,11 @@ class TrackerControllerImpl: Controller, TrackerController {

var advertisingIdentifierRetriever: (() -> UUID?)? {
get {
return tracker.advertisingIdentifierRetriever
return tracker.subject?.advertisingIdentifierRetriever
}
set {
dirtyConfig.advertisingIdentifierRetriever = newValue
tracker.advertisingIdentifierRetriever = newValue
tracker.subject?.advertisingIdentifierRetriever = newValue
}
}

Expand Down
2 changes: 2 additions & 0 deletions Sources/Core/TrackerConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ let kSPMobileAppAvailableMemory = "appAvailableMemory"
let kSPMobileBatteryLevel = "batteryLevel"
let kSPMobileBatteryState = "batteryState"
let kSPMobileLowPowerMode = "lowPowerMode"
let kSPMobileAvailableStorage = "availableStorage"
let kSPMobileTotalStorage = "totalStorage"
let kSPMobileIsPortrait = "isPortrait"
let kSPMobileResolution = "resolution"
let kSPMobileLanguage = "language"
Expand Down
10 changes: 5 additions & 5 deletions Sources/Core/Utils/DeviceInfoMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ class DeviceInfoMonitor {

/// Returns the current device's vendor in the form of a string.
/// - Returns: A string with vendor, i.e. "Apple Inc."
var deviceVendor: String? {
var deviceVendor: String {
return "Apple Inc."
}

/// Returns the current device's model in the form of a string.
/// - Returns: A string with device model.
var deviceModel: String? {
var deviceModel: String {
let simulatorModel = (ProcessInfo.processInfo.environment)["SIMULATOR_MODEL_IDENTIFIER"]
if simulatorModel != nil {
if let simulatorModel = simulatorModel {
return simulatorModel
}

Expand All @@ -59,7 +59,7 @@ class DeviceInfoMonitor {

/// This is to detect what the version of mobile OS of the current device.
/// - Returns: The current device's OS version type as a string.
var osVersion: String? {
var osVersion: String {
#if os(iOS) || os(tvOS) || os(visionOS)
return UIDevice.current.systemVersion
#elseif os(watchOS)
Expand All @@ -78,7 +78,7 @@ class DeviceInfoMonitor {
#endif
}

var osType: String? {
var osType: String {
#if os(iOS)
return "ios"
#elseif os(tvOS)
Expand Down
Loading

0 comments on commit 20231c6

Please sign in to comment.