diff --git a/Makefile b/Makefile index a17e2e479..dce8018d0 100644 --- a/Makefile +++ b/Makefile @@ -3,10 +3,10 @@ build: buildSdk buildExample buildSdk: - set -o pipefail && xcodebuild build -project PostHog.xcodeproj -scheme PostHog -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.0' | xcpretty + set -o pipefail && xcodebuild build -project PostHog.xcodeproj -scheme PostHog -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.0.1' | xcpretty buildExample: - set -o pipefail && xcodebuild build -project PostHog.xcodeproj -scheme PostHogExample -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.0' | xcpretty + set -o pipefail && xcodebuild build -project PostHog.xcodeproj -scheme PostHogExample -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.0.1' | xcpretty format: swiftLint swiftFormat @@ -17,7 +17,7 @@ swiftFormat: swiftformat . --swiftversion 5.3 test: - set -o pipefail && xcodebuild test -project PostHog.xcodeproj -scheme PostHog -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.0' | xcpretty + set -o pipefail && xcodebuild test -project PostHog.xcodeproj -scheme PostHog -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.0.1' | xcpretty lint: swiftformat . --lint --swiftversion 5.3 && swiftlint diff --git a/PostHog.xcodeproj/project.pbxproj b/PostHog.xcodeproj/project.pbxproj index bed1b3a41..9e1674d43 100644 --- a/PostHog.xcodeproj/project.pbxproj +++ b/PostHog.xcodeproj/project.pbxproj @@ -11,7 +11,6 @@ 3A0F108529C9ABB6002C0084 /* ReadWriteLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A0F108429C9ABB6002C0084 /* ReadWriteLock.swift */; }; 3A0F108929C9BD76002C0084 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A0F108829C9BD76002C0084 /* Errors.swift */; }; 3A2BCF4C299E4E35008BB5F3 /* QueueTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2BCF4B299E4E35008BB5F3 /* QueueTest.swift */; }; - 3A2BCF52299F7724008BB5F3 /* CodableUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2BCF51299F7724008BB5F3 /* CodableUtils.swift */; }; 3A580B3F29E481F200C5C6F3 /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = 3A580B3E29E481F200C5C6F3 /* OHHTTPStubs */; }; 3A580B4129E481F200C5C6F3 /* OHHTTPStubsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 3A580B4029E481F200C5C6F3 /* OHHTTPStubsSwift */; }; 3A580B4329E489D000C5C6F3 /* URLSession+body.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A580B4229E489D000C5C6F3 /* URLSession+body.swift */; }; @@ -44,6 +43,7 @@ 3AE3FB4B2993A68500AFFC18 /* StorageTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE3FB4A2993A68500AFFC18 /* StorageTest.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 */; }; + 690FF0B52AEBBD3C00A0B06B /* DictUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 690FF0B42AEBBD3C00A0B06B /* DictUtils.swift */; }; 690FF0AF2AEB9C1400A0B06B /* DateUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 690FF0AE2AEB9C1400A0B06B /* DateUtils.swift */; }; 690FF0C52AEFAE8200A0B06B /* PostHogLegacyQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 690FF0C42AEFAE8200A0B06B /* PostHogLegacyQueue.swift */; }; 69261D132AD5685B00232EC7 /* PostHogFeatureFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69261D122AD5685B00232EC7 /* PostHogFeatureFlags.swift */; }; @@ -141,7 +141,6 @@ 3A0F108429C9ABB6002C0084 /* ReadWriteLock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadWriteLock.swift; sourceTree = ""; }; 3A0F108829C9BD76002C0084 /* Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; 3A2BCF4B299E4E35008BB5F3 /* QueueTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueueTest.swift; sourceTree = ""; }; - 3A2BCF51299F7724008BB5F3 /* CodableUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableUtils.swift; sourceTree = ""; }; 3A580B4229E489D000C5C6F3 /* URLSession+body.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSession+body.swift"; sourceTree = ""; }; 3A62646329C9E0E7007E8C07 /* PostHogTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogTest.swift; sourceTree = ""; }; 3A62646929C9E385007E8C07 /* MockPostHogServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPostHogServer.swift; sourceTree = ""; }; @@ -172,6 +171,7 @@ 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 = ""; }; + 690FF0B42AEBBD3C00A0B06B /* DictUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DictUtils.swift; sourceTree = ""; }; 690FF0AE2AEB9C1400A0B06B /* DateUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateUtils.swift; sourceTree = ""; }; 690FF0C42AEFAE8200A0B06B /* PostHogLegacyQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogLegacyQueue.swift; sourceTree = ""; }; 69261D122AD5685B00232EC7 /* PostHogFeatureFlags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogFeatureFlags.swift; sourceTree = ""; }; @@ -282,10 +282,10 @@ children = ( 3AE3FB422992985A00AFFC18 /* Reachability.swift */, 3AE3FB462992AB0000AFFC18 /* Hedgelog.swift */, - 3A2BCF51299F7724008BB5F3 /* CodableUtils.swift */, 3A0F108429C9ABB6002C0084 /* ReadWriteLock.swift */, 3A0F108829C9BD76002C0084 /* Errors.swift */, 690FF05E2AE7E2D400A0B06B /* Data+Gzip.swift */, + 690FF0B42AEBBD3C00A0B06B /* DictUtils.swift */, 690FF0AE2AEB9C1400A0B06B /* DateUtils.swift */, ); path = Utils; @@ -636,7 +636,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3A2BCF52299F7724008BB5F3 /* CodableUtils.swift in Sources */, 690FF05F2AE7E2D400A0B06B /* Data+Gzip.swift in Sources */, 69261D1F2AD9681300232EC7 /* PostHogConsumerPayload.swift in Sources */, 69261D252AD9787A00232EC7 /* PostHogExtensions.swift in Sources */, @@ -654,6 +653,7 @@ 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 */, diff --git a/PostHog/PostHogSDK.swift b/PostHog/PostHogSDK.swift index fbd0217d8..b66bb74c7 100644 --- a/PostHog/PostHogSDK.swift +++ b/PostHog/PostHogSDK.swift @@ -226,10 +226,14 @@ let maxRetryDelay = 30.0 return } + let sanitizedProps = sanitizeDicionary(properties) + if sanitizedProps == nil { + return + } + personPropsLock.withLock { - // TODO: Sanitise props for storage let props = getRegisteredProperties() - let mergedProps = props.merging(properties) { _, new in new } + let mergedProps = props.merging(sanitizedProps!) { _, new in new } storage?.setDictionary(forKey: .registerProperties, contents: mergedProps) } } @@ -274,7 +278,7 @@ let maxRetryDelay = 30.0 properties: buildProperties(properties: [ "distinct_id": distinctId, "$anon_distinct_id": getAnonymousId(), - ], userProperties: userProperties, userPropertiesSetOnce: userPropertiesSetOnce) + ], userProperties: sanitizeDicionary(userProperties), userPropertiesSetOnce: sanitizeDicionary(userPropertiesSetOnce)) )) if distinctId != oldDistinctId { @@ -331,10 +335,10 @@ let maxRetryDelay = 30.0 queue.add(PostHogEvent( event: event, distinctId: getDistinctId(), - properties: buildProperties(properties: properties, - userProperties: userProperties, - userPropertiesSetOnce: userPropertiesSetOnce, - groupProperties: groupProperties) + properties: buildProperties(properties: sanitizeDicionary(properties), + userProperties: sanitizeDicionary(userProperties), + userPropertiesSetOnce: sanitizeDicionary(userPropertiesSetOnce), + groupProperties: sanitizeDicionary(groupProperties)) )) } @@ -354,7 +358,7 @@ let maxRetryDelay = 30.0 let props = [ "$screen_name": screenTitle, - ].merging(properties ?? [:]) { prop, _ in prop } + ].merging(sanitizeDicionary(properties) ?? [:]) { prop, _ in prop } queue.add(PostHogEvent( event: "$screen", @@ -417,15 +421,21 @@ let maxRetryDelay = 30.0 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: [ - "$group_type": type, - "$group_key": key, - "$group_set": groupProperties ?? [], - ]) + properties: buildProperties(properties: props) )) } @@ -443,7 +453,7 @@ let maxRetryDelay = 30.0 _ = groups([type: key]) if groupProperties != nil { - groupIdentify(type: type, key: key, groupProperties: groupProperties) + groupIdentify(type: type, key: key, groupProperties: sanitizeDicionary(groupProperties)) } } diff --git a/PostHog/Utils/CodableUtils.swift b/PostHog/Utils/CodableUtils.swift deleted file mode 100644 index 04d8cd583..000000000 --- a/PostHog/Utils/CodableUtils.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// CodableUtils.swift -// PostHog -// -// Created by Ben White on 17.02.23. -// - -import Foundation - -@propertyWrapper -public struct CodableIgnored: Codable { - public var wrappedValue: T? - - public init(wrappedValue: T?) { - self.wrappedValue = wrappedValue - } - - public init(from _: Decoder) throws { - wrappedValue = nil - } - - public func encode(to _: Encoder) throws { - // Do nothing - } -} - -public extension KeyedDecodingContainer { - func decode( - _: CodableIgnored.Type, - forKey _: Self.Key - ) throws -> CodableIgnored { - CodableIgnored(wrappedValue: nil) - } -} - -public extension KeyedEncodingContainer { - mutating func encode( - _: CodableIgnored, - forKey _: KeyedEncodingContainer.Key - ) throws { - // Do nothing - } -} 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/PostHogTests/FeatureFlagsTest.swift b/PostHogTests/FeatureFlagsTest.swift index 339d5b675..db6f011d7 100644 --- a/PostHogTests/FeatureFlagsTest.swift +++ b/PostHogTests/FeatureFlagsTest.swift @@ -20,7 +20,7 @@ class FeatureFlagTests: QuickSpec { posthog = harness.posthog let expectation = self.expectation(description: "Waits for flags") - posthog.reloadFeatureFlags { _, _ in + posthog.reloadFeatureFlags { expectation.fulfill() }