Skip to content

Commit

Permalink
chore: add is identified property (#186)
Browse files Browse the repository at this point in the history
  • Loading branch information
marandaneto authored Sep 9, 2024
1 parent ac6d287 commit 19c997b
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 25 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## Next

- recording: mask swiftui picker if masking enabled ([#184](https://github.com/PostHog/posthog-ios/pull/184))
- chore: add is identified property ([#186](https://github.com/PostHog/posthog-ios/pull/186))

## 3.9.1 - 2024-09-06

Expand Down
53 changes: 35 additions & 18 deletions PostHog/PostHogSDK.swift
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,10 @@ let maxRetryDelay = 30.0
let mergedGroups = currentGroups.merging(groups ?? [:]) { current, _ in current }
props["$groups"] = mergedGroups
}

if let isIdentified = storageManager?.isIdentified() {
props["$is_identified"] = isIdentified
}
}

if let sessionId = PostHogSessionManager.shared.getSessionId() {
Expand Down Expand Up @@ -313,6 +317,7 @@ let maxRetryDelay = 30.0

// storage also removes all feature flags
storage?.reset()
storageManager?.reset()
flagCallReported.removeAll()
PostHogSessionManager.shared.endSession {
self.resetViews()
Expand Down Expand Up @@ -393,33 +398,44 @@ let maxRetryDelay = 30.0
return
}

if distinctId.isEmpty {
hedgeLog("identify call not allowed, distinctId is invalid: \(distinctId)")
return
}

if isOptOutState() {
return
}

guard let queue = queue, let sessionManager = storageManager else {
guard let queue = queue, let storageManager = storageManager else {
return
}
let oldDistinctId = getDistinctId()

let properties = buildProperties(distinctId: distinctId, properties: [
"distinct_id": distinctId,
"$anon_distinct_id": getAnonymousId(),
], userProperties: sanitizeDicionary(userProperties), userPropertiesSetOnce: sanitizeDicionary(userPropertiesSetOnce))
let sanitizedProperties = sanitizeProperties(properties)

queue.add(PostHogEvent(
event: "$identify",
distinctId: distinctId,
properties: sanitizedProperties
))
let isIdentified = storageManager.isIdentified()

if distinctId != oldDistinctId {
if distinctId != oldDistinctId, !isIdentified {
// We keep the AnonymousId to be used by decide calls and identify to link the previousId
sessionManager.setAnonymousId(oldDistinctId)
sessionManager.setDistinctId(distinctId)
storageManager.setAnonymousId(oldDistinctId)
storageManager.setDistinctId(distinctId)

storageManager.setIdentified(true)

let properties = buildProperties(distinctId: distinctId, properties: [
"distinct_id": distinctId,
"$anon_distinct_id": oldDistinctId,
], userProperties: sanitizeDicionary(userProperties), userPropertiesSetOnce: sanitizeDicionary(userPropertiesSetOnce))
let sanitizedProperties = sanitizeProperties(properties)

queue.add(PostHogEvent(
event: "$identify",
distinctId: distinctId,
properties: sanitizedProperties
))

reloadFeatureFlags()
} else {
hedgeLog("already identified with id: \(oldDistinctId)")
}
}

Expand Down Expand Up @@ -682,7 +698,7 @@ let maxRetryDelay = 30.0
return
}

guard let featureFlags = featureFlags, let sessionManager = storageManager else {
guard let featureFlags = featureFlags, let storageManager = storageManager else {
return
}

Expand All @@ -691,8 +707,8 @@ let maxRetryDelay = 30.0
groups = getGroups()
}
featureFlags.loadFeatureFlags(
distinctId: sessionManager.getDistinctId(),
anonymousId: sessionManager.getAnonymousId(),
distinctId: storageManager.getDistinctId(),
anonymousId: storageManager.getAnonymousId(),
groups: groups ?? [:],
callback: callback
)
Expand Down Expand Up @@ -819,6 +835,7 @@ let maxRetryDelay = 30.0
#endif
queue = nil
replayQueue = nil
storageManager?.reset()
storageManager = nil
config = PostHogConfig(apiKey: "")
api = nil
Expand Down
2 changes: 2 additions & 0 deletions PostHog/PostHogStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class PostHogStorage {
case registerProperties = "posthog.registerProperties"
case optOut = "posthog.optOut"
case sessionReplay = "posthog.sessionReplay"
case isIdentified = "posthog.isIdentified"
}

private let config: PostHogConfig
Expand Down Expand Up @@ -139,6 +140,7 @@ class PostHogStorage {
deleteSafely(url(forKey: .registerProperties))
deleteSafely(url(forKey: .optOut))
deleteSafely(url(forKey: .sessionReplay))
deleteSafely(url(forKey: .isIdentified))
}

public func remove(key: StorageKey) {
Expand Down
36 changes: 33 additions & 3 deletions PostHog/PostHogStorageManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ class PostHogStorageManager {

private let anonLock = NSLock()
private let distinctLock = NSLock()
private let identifiedLock = NSLock()
private let idGen: (UUID) -> UUID

private var distinctId: String?
private var cachedDistinctId = false
private var anonymousId: String?
private var isIdentifiedValue: Bool?

init(_ config: PostHogConfig) {
storage = PostHogStorage(config)
Expand Down Expand Up @@ -87,15 +89,43 @@ class PostHogStorageManager {
}
}

public func reset() {
public func isIdentified() -> Bool {
identifiedLock.withLock {
if isIdentifiedValue == nil {
isIdentifiedValue = storage.getBool(forKey: .isIdentified) ?? (getDistinctId() != getDistinctId())
}
}
return isIdentifiedValue ?? false
}

public func setIdentified(_ isIdentified: Bool) {
identifiedLock.withLock {
isIdentifiedValue = isIdentified
storage.setBool(forKey: .isIdentified, contents: isIdentified)
}
}

public func reset(_ resetStorage: Bool = false) {
// resetStorage is only used for testing, when the reset method is called,
// the storage is also cleared, so we dont do here to not do it twice.
distinctLock.withLock {
storage.remove(key: .distinctId)
distinctId = nil
cachedDistinctId = false
if resetStorage {
storage.remove(key: .distinctId)
}
}
anonLock.withLock {
storage.remove(key: .anonymousId)
anonymousId = nil
if resetStorage {
storage.remove(key: .anonymousId)
}
}
identifiedLock.withLock {
isIdentifiedValue = nil
if resetStorage {
storage.remove(key: .isIdentified)
}
}
}
}
2 changes: 1 addition & 1 deletion PostHogTests/PostHogFeatureFlagsTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ class PostHogFeatureFlagsTest: QuickSpec {

group.wait()

expect(storage.getDictionary(forKey: .sessionReplay) == nil)
expect(storage.getDictionary(forKey: .sessionReplay)) == nil
expect(sut.isSessionReplayFlagActive()) == false

storage.reset()
Expand Down
53 changes: 53 additions & 0 deletions PostHogTests/PostHogSDKTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,59 @@ class PostHogSDKTest: QuickSpec {
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

let set = event.properties["$set"] as? [String: Any] ?? [:]
expect(set["userProp"] as? String) == "value"

let setOnce = event.properties["$set_once"] as? [String: Any] ?? [:]
expect(setOnce["userPropOnce"] as? String) == "value"

sut.reset()
sut.close()
}

it("captures an event with is identified false") {
let sut = self.getSut()

sut.capture("test",
userProperties: ["userProp": "value"],
userPropertiesSetOnce: ["userPropOnce": "value"])

let events = getBatchedEvents(server)

expect(events.count) == 1

let event = events.first!

expect(event.properties["$is_identified"] as? Bool) == false

sut.reset()
sut.close()
}

it("does not capture identify event if already identified") {
let sut = self.getSut()

sut.identify("distinctId",
userProperties: ["userProp": "value"],
userPropertiesSetOnce: ["userPropOnce": "value"])

sut.identify("distinctId",
userProperties: ["userProp2": "value2"],
userPropertiesSetOnce: ["userPropOnce2": "value2"])

let events = getBatchedEvents(server)

expect(events.count) == 1

let event = events.first!
expect(event.event) == "$identify"

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

let set = event.properties["$set"] as? [String: Any] ?? [:]
expect(set["userProp"] as? String) == "value"
Expand Down
6 changes: 3 additions & 3 deletions PostHogTests/PostHogStorageManagerTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class PostHogStorageManagerTest: QuickSpec {
let secondAnonymousId = sut.getAnonymousId()
expect(secondAnonymousId) == anonymousId

sut.reset()
sut.reset(true)
}

it("Uses the anonymousId for distinctId if not set") {
Expand All @@ -43,7 +43,7 @@ class PostHogStorageManagerTest: QuickSpec {
expect(newAnonymousId) != newDistinctId
expect(newDistinctId) == idToSet

sut.reset()
sut.reset(true)
}

it("Can can accept id customization via config") {
Expand All @@ -54,7 +54,7 @@ class PostHogStorageManagerTest: QuickSpec {
let anonymousId = sut.getAnonymousId()
expect(anonymousId) == fixedUuid.uuidString

sut.reset()
sut.reset(true)
}
}
}

0 comments on commit 19c997b

Please sign in to comment.