diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d9a854b1..1dea90e69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## Next +- fix: allow changing person properties after identify ([#249](https://github.com/PostHog/posthog-ios/pull/249)) + ## 3.15.1 - 2024-11-12 - fix: accessing UI APIs off main thread to get screen size ([#247](https://github.com/PostHog/posthog-ios/pull/247)) diff --git a/PostHog/PostHogSDK.swift b/PostHog/PostHogSDK.swift index 7c2df7509..e3be74d32 100644 --- a/PostHog/PostHogSDK.swift +++ b/PostHog/PostHogSDK.swift @@ -468,7 +468,9 @@ let maxRetryDelay = 30.0 let isIdentified = storageManager.isIdentified() - if distinctId != oldDistinctId, !isIdentified { + let hasDifferentDistinctId = distinctId != oldDistinctId + + if hasDifferentDistinctId, !isIdentified { // We keep the AnonymousId to be used by decide calls and identify to link the previousId storageManager.setAnonymousId(oldDistinctId) storageManager.setDistinctId(distinctId) @@ -490,6 +492,16 @@ let maxRetryDelay = 30.0 if shouldReloadFlagsForTesting { reloadFeatureFlags() } + // we need to make sure the user props update is for the same user + // otherwise they have to reset and identify again + } else if !hasDifferentDistinctId, !(userProperties?.isEmpty ?? true) || !(userPropertiesSetOnce?.isEmpty ?? true) { + capture("$set", + distinctId: distinctId, + userProperties: userProperties, + userPropertiesSetOnce: userPropertiesSetOnce) + + // Note we don't reload flags on property changes as these get processed async + } else { hedgeLog("already identified with id: \(oldDistinctId)") } diff --git a/PostHogTests/PostHogSDKTest.swift b/PostHogTests/PostHogSDKTest.swift index b06fd768d..39d7665c5 100644 --- a/PostHogTests/PostHogSDKTest.swift +++ b/PostHogTests/PostHogSDKTest.swift @@ -156,7 +156,43 @@ class PostHogSDKTest: QuickSpec { } it("does not capture identify event if already identified") { - let sut = self.getSut() + let sut = self.getSut( + flushAt: 2 + ) + + sut.identify("distinctId", + userProperties: ["userProp": "value"], + userPropertiesSetOnce: ["userPropOnce": "value"]) + + sut.identify("distinctId") + sut.capture("satisfy_queue") + + let events = getBatchedEvents(server) + + expect(events.count) == 2 + + expect(events[0].event) == "$identify" + expect(events[1].event) == "satisfy_queue" + + expect(events[0].distinctId) == "distinctId" + let anonId = sut.getAnonymousId() + expect(events[0].properties["$anon_distinct_id"] as? String) == anonId + expect(events[0].properties["$is_identified"] as? Bool) == true + + let set = events[0].properties["$set"] as? [String: Any] ?? [:] + expect(set["userProp"] as? String) == "value" + + let setOnce = events[0].properties["$set_once"] as? [String: Any] ?? [:] + expect(setOnce["userPropOnce"] as? String) == "value" + + sut.reset() + sut.close() + } + + it("updates user props if already identified but user properties are set") { + let sut = self.getSut( + flushAt: 2 + ) sut.identify("distinctId", userProperties: ["userProp": "value"], @@ -168,20 +204,65 @@ class PostHogSDKTest: QuickSpec { let events = getBatchedEvents(server) - expect(events.count) == 1 + expect(events.count) == 2 - let event = events.first! - expect(event.event) == "$identify" + expect(events[0].event) == "$identify" + expect(events[1].event) == "$set" + + expect(events[0].distinctId) == "distinctId" + expect(events[1].distinctId) == events[0].distinctId - expect(event.distinctId) == "distinctId" let anonId = sut.getAnonymousId() - expect(event.properties["$anon_distinct_id"] as? String) == anonId - expect(event.properties["$is_identified"] as? Bool) == true + expect(events[0].properties["$anon_distinct_id"] as? String) == anonId + expect(events[0].properties["$is_identified"] as? Bool) == true - let set = event.properties["$set"] as? [String: Any] ?? [:] + let set0 = events[0].properties["$set"] as? [String: Any] ?? [:] + expect(set0["userProp"] as? String) == "value" + + let set1 = events[1].properties["$set"] as? [String: Any] ?? [:] + expect(set1["userProp2"] as? String) == "value2" + + let setOnce0 = events[0].properties["$set_once"] as? [String: Any] ?? [:] + expect(setOnce0["userPropOnce"] as? String) == "value" + + let setOnce1 = events[1].properties["$set_once"] as? [String: Any] ?? [:] + expect(setOnce1["userPropOnce2"] as? String) == "value2" + + sut.reset() + sut.close() + } + + it("does not capture user props for another distinctId even if user properties are set") { + let sut = self.getSut( + flushAt: 2 + ) + + sut.identify("distinctId", + userProperties: ["userProp": "value"], + userPropertiesSetOnce: ["userPropOnce": "value"]) + + sut.identify("distinctId2", + userProperties: ["userProp2": "value2"], + userPropertiesSetOnce: ["userPropOnce2": "value2"]) + + sut.capture("satisfy_queue") + + let events = getBatchedEvents(server) + + expect(events.count) == 2 + + expect(events[0].event) == "$identify" + expect(events[1].event) == "satisfy_queue" + + expect(events[0].distinctId) == "distinctId" + let anonId = sut.getAnonymousId() + expect(events[0].properties["$anon_distinct_id"] as? String) == anonId + expect(events[0].properties["$is_identified"] as? Bool) == true + + let set = events[0].properties["$set"] as? [String: Any] ?? [:] expect(set["userProp"] as? String) == "value" - let setOnce = event.properties["$set_once"] as? [String: Any] ?? [:] + let setOnce = events[0].properties["$set_once"] as? [String: Any] ?? [:] expect(setOnce["userPropOnce"] as? String) == "value" sut.reset()