Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add is identified property #186

Merged
merged 8 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
}
}
Loading