Skip to content

Commit

Permalink
add captureApplicationLifecycleEvents
Browse files Browse the repository at this point in the history
  • Loading branch information
marandaneto committed Oct 23, 2023
1 parent b01cd5b commit d10ac7b
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 82 deletions.
3 changes: 2 additions & 1 deletion .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ excluded: # case-sensitive paths to ignore during linting. Takes precedence over
- PostHog/Utils/NSData+PHGGZIP.h
- PostHog/Utils/NSData+PHGGZIP.m
- .build
- Pods

disabled_rules:
- force_cast
Expand All @@ -17,7 +18,7 @@ disabled_rules:

line_length: 160
file_length:
warning: 600
warning: 1000
error: 1200

function_body_length:
Expand Down
4 changes: 2 additions & 2 deletions PostHog/PostHogConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ import Foundation
@objc public var dataMode: PostHogDataMode = .any
@objc public var sendFeatureFlagEvent: Bool = true
@objc public var preloadFeatureFlags: Bool = true
@objc public var captureApplicationLifecycleEvents: Bool = true
@objc public var captureScreenViews: Bool = true
@objc public var debug: Bool = false
@objc public var optOut: Bool = false
public static let defaultHost: String = "https://app.posthog.com"
// TODO: encryption, captureApplicationLifecycleEvents, recordScreenViews, captureInAppPurchases,
// capturePushNotifications, captureDeepLinks, launchOptions

public init(
apiKey: String,
Expand Down
111 changes: 99 additions & 12 deletions PostHog/PostHogSDK.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ let maxRetryDelay = 30.0
private var featureFlags: PostHogFeatureFlags?
private var context: PostHogContext?
private static var apiKeys = Set<String>()
private var capturedAppInstalled = false

@objc public static let shared: PostHogSDK = {
let instance = PostHogSDK(PostHogConfig(apiKey: ""))
Expand Down Expand Up @@ -90,11 +91,10 @@ let maxRetryDelay = 30.0

queue = PostHogQueue(config, theStorage, theApi, reachability)

// TODO: Decide if we definitely want to reset the session on load or not
sessionManager?.resetSession()

queue?.start()

registerNotifications()

DispatchQueue.main.async {
NotificationCenter.default.post(name: PostHogSDK.didStartNotification, object: nil)
}
Expand All @@ -121,20 +121,11 @@ let maxRetryDelay = 30.0
return sessionManager?.getAnonymousId() ?? ""
}

@objc public func getSessionId() -> String? {
if !isEnabled() {
return nil
}

return sessionManager?.getSessionId()
}

// EVENT CAPTURE

private func dynamicContext() -> [String: Any] {
var properties: [String: Any] = [:]

properties["$session_id"] = getSessionId()
var groups: [String: String]?
groupsLock.withLock {
groups = getGroups()
Expand Down Expand Up @@ -515,4 +506,100 @@ let maxRetryDelay = 30.0
postHog.setup(config)
return postHog
}

private func registerNotifications() {
let defaultCenter = NotificationCenter.default

#if os(iOS) || os(tvOS)
let didFinishLaunchingNotification = UIApplication.didFinishLaunchingNotification

defaultCenter.addObserver(self, selector: #selector(captureAppLifecycle), name: didFinishLaunchingNotification, object: nil)
#endif
}

private func captureAppInstalled() {
let bundle = Bundle.main

let versionName = bundle.infoDictionary?["CFBundleShortVersionString"] as? String
let versionCode = bundle.infoDictionary?["CFBundleVersion"] as? String

// capture app installed/updated
if !capturedAppInstalled {
let userDefaults = UserDefaults.standard

let previousVersion = userDefaults.string(forKey: "PHGVersionKey")
let previousVersionCode = userDefaults.string(forKey: "PHGBuildKeyV2")

var props: [String: Any] = [:]
var event: String
if previousVersionCode == nil {
// installed
event = "Application Installed"
} else {
event = "Application Updated"

// Do not send version updates if its the same
if previousVersionCode == versionCode {
return
}

if previousVersion != nil {
props["previous_version"] = previousVersion
}
props["previous_build"] = previousVersionCode
}

var syncDefaults = false
if versionName != nil {
props["version"] = versionName
userDefaults.setValue(versionName, forKey: "PHGVersionKey")
syncDefaults = true
}

if versionCode != nil {
props["build"] = versionCode
userDefaults.setValue(versionCode, forKey: "PHGBuildKeyV2")
syncDefaults = true
}

if syncDefaults {
userDefaults.synchronize()
}

capture(event, properties: props)

capturedAppInstalled = true
}
}

private func captureAppOpened() {
let bundle = Bundle.main

let versionName = bundle.infoDictionary?["CFBundleShortVersionString"] as? String
let versionCode = bundle.infoDictionary?["CFBundleVersion"] as? String

var props: [String: Any] = [:]

if versionName != nil {
props["version"] = versionName
}
if versionCode != nil {
props["build"] = versionCode
}
// TODO: detect info dynamically
props["from_background"] = false
// props["referring_application"] = launchOptions[UIApplicationLaunchOptionsSourceApplicationKey]
// props["url"] = launchOptions[UIApplicationLaunchOptionsURLKey]

capture("Application Opened", properties: props)
}

@objc private func captureAppLifecycle() {
if !config.captureApplicationLifecycleEvents {
return
}

captureAppInstalled()
captureAppOpened()
}
}
39 changes: 0 additions & 39 deletions PostHog/PostHogSessionManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,10 @@ class PostHogSessionManager {

private let anonLock = NSLock()
private let distinctLock = NSLock()
private let sessionLock = NSLock()

init(config: PostHogConfig) {
storage = PostHogStorage(config)
}

let sessionChangeThreshold: Double = 1800

public func getAnonymousId() -> String {
var anonymousId: String?
anonLock.withLock {
Expand Down Expand Up @@ -57,39 +53,4 @@ class PostHogSessionManager {
storage.setString(forKey: .distinctId, contents: id)
}
}

public func getSessionId() -> String {
var sessionId: String?
sessionLock.withLock {
sessionId = getSesssionId()
}
return sessionId ?? ""
}

// Load the sessionId, ensuring it is rotated if expired
private func getSesssionId(timestamp: TimeInterval? = nil) -> String {
var sessionId = storage.getString(forKey: .sessionId)
let sessionLastTimestamp = storage.getNumber(forKey: .sessionlastTimestamp) ?? 0
let newTimestamp = Double(timestamp ?? Date().timeIntervalSince1970)

if sessionId == nil || sessionLastTimestamp == 0 || (newTimestamp - sessionLastTimestamp) > sessionChangeThreshold {
sessionId = UUID().uuidString
storage.setString(forKey: .sessionId, contents: sessionId!)
storage.setNumber(forKey: .sessionlastTimestamp, contents: newTimestamp)

hedgeLog("Session expired - creating new session '\(sessionId!)'")
DispatchQueue.main.async {
NotificationCenter.default.post(name: PostHogSDK.didResetSessionNotification, object: sessionId)
}
}

return sessionId ?? ""
}

public func resetSession() {
sessionLock.withLock {
storage.remove(key: .sessionId)
storage.remove(key: .sessionlastTimestamp)
}
}
}
1 change: 0 additions & 1 deletion PostHog/PostHogStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ class PostHogStorage {
case enabledFeatureFlags = "posthog.enabledFeatureFlags"
case enabledFeatureFlagPayloads = "posthog.enabledFeatureFlagPayloads"
case groups = "posthog.groups"
case sessionId = "posthog.sessionId"
case sessionlastTimestamp = "posthog.sessionlastTimestamp"
case registerProperties = "posthog.registerProperties"
case optOut = "posthog.optOut"
Expand Down
4 changes: 2 additions & 2 deletions PostHogExample/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ class AppDelegate: NSObject, UIApplicationDelegate {
)

PostHogSDK.shared.setup(config)
PostHogSDK.shared.debug()
PostHogSDK.shared.capture("App started!")
// PostHogSDK.shared.debug()
// PostHogSDK.shared.capture("App started!")

// DispatchQueue.global(qos: .utility).async {
// let task = Api().failingRequest()
Expand Down
4 changes: 0 additions & 4 deletions PostHogTests/PostHogTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,17 @@ class PostHogTest: QuickSpec {
it("setups default IDs") {
expect(posthog.getAnonymousId()).toNot(beNil())
expect(posthog.getDistinctId()) == posthog.getAnonymousId()
expect(posthog.getSessionId()).toNot(beNil())
}

it("persits IDs but resets the session ID on load") {
let anonymousId = posthog.getAnonymousId()
let distinctId = posthog.getDistinctId()
let sessionId = posthog.getSessionId()

let config = PostHogConfig(apiKey: "test-api-key")
let otherPostHog = PostHogSDK.with(config)

let otherAnonymousId = otherPostHog.getAnonymousId()
let otherDistinctId = otherPostHog.getDistinctId()
let otherSessionId = otherPostHog.getSessionId()
let refreshedSessionId = posthog.getSessionId()

expect(anonymousId) == otherAnonymousId
expect(distinctId) == otherDistinctId
Expand Down
21 changes: 0 additions & 21 deletions PostHogTests/SessionManagerTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,26 +45,5 @@ class SessionManagerTest: QuickSpec {
expect(newAnonymousId) != newDistinctId
expect(newDistinctId) == idToSet
}

it("Generates a session id") {
let sessionId = sessionManager.getSessionId()
expect(sessionId).toNot(beNil())

let secondSessionId = sessionManager.getSessionId()
expect(secondSessionId) == sessionId
}

it("Generates a new session id if last timestamp is older") {
let sessionId = sessionManager.getSessionId()
expect(sessionId).toNot(beNil())

changeSessionLastTimestamp(timeToAdd: TimeInterval(0 - sessionManager.sessionChangeThreshold + 100))
let sameSessionId = sessionManager.getSessionId()
expect(sameSessionId) == sessionId

changeSessionLastTimestamp(timeToAdd: TimeInterval(0 - sessionManager.sessionChangeThreshold - 100))
let newSessionId = sessionManager.getSessionId()
expect(newSessionId) != sessionId
}
}
}

0 comments on commit d10ac7b

Please sign in to comment.