-
Notifications
You must be signed in to change notification settings - Fork 26
feat: auto-rotate session after 24 hours #247
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
package com.posthog | ||
|
||
import com.posthog.internal.MAX_SESSION_AGE_MILLIS | ||
import com.posthog.internal.PostHogApi | ||
import com.posthog.internal.PostHogApiEndpoint | ||
import com.posthog.internal.PostHogMemoryPreferences | ||
|
@@ -432,16 +433,18 @@ public class PostHog private constructor( | |
} | ||
|
||
val mergedProperties = | ||
buildProperties( | ||
newDistinctId, | ||
properties = properties, | ||
userProperties = userProperties, | ||
userPropertiesSetOnce = userPropertiesSetOnce, | ||
groups = groups, | ||
// only append shared props if not a snapshot event | ||
appendSharedProps = !snapshotEvent, | ||
// only append groups if not a group identify event and not a snapshot | ||
appendGroups = !groupIdentify, | ||
enforceMaxSessionLength( | ||
buildProperties( | ||
newDistinctId, | ||
properties = properties, | ||
userProperties = userProperties, | ||
userPropertiesSetOnce = userPropertiesSetOnce, | ||
groups = groups, | ||
// only append shared props if not a snapshot event | ||
appendSharedProps = !snapshotEvent, | ||
// only append groups if not a group identify event and not a snapshot | ||
appendGroups = !groupIdentify, | ||
), | ||
) | ||
|
||
// sanitize the properties or fallback to the original properties | ||
|
@@ -466,6 +469,35 @@ public class PostHog private constructor( | |
} | ||
} | ||
|
||
/** | ||
* Because we use UUIDv7 | ||
* we can convert a session id into the session start timestamp | ||
* and then use that to enforce the maximum session length | ||
*/ | ||
private fun enforceMaxSessionLength(buildProperties: Map<String, Any>): Map<String, Any> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. react native has its own session id rotation so we should not do this for RN I think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so, needs to be configurable? and then RN would turn off the Android one? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, we have something similar for flutter already |
||
val sessionId = buildProperties["\$session_id"] ?: return buildProperties | ||
|
||
val sessionStartTimestamp = | ||
TimeBasedEpochGenerator.getTimestampFromUuid(UUID.fromString(sessionId.toString())) | ||
?: return buildProperties | ||
|
||
val currentTimeMillis = | ||
config?.dateProvider?.currentTimeMillis() ?: System.currentTimeMillis() | ||
val sessionAge = currentTimeMillis - sessionStartTimestamp | ||
if (sessionAge > MAX_SESSION_AGE_MILLIS) { | ||
// rotate session | ||
// update properties | ||
PostHogSessionManager.endSession() | ||
PostHogSessionManager.startSession(currentTimeMillis) | ||
Comment on lines
+490
to
+491
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this changes the session id but the session replay would need to be notified to eg send a new full snapshot with metadata etc otherwise it'll assume its already sent and the replay will be broken (missing meta event and full snapshot for the new session id) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤔 in the JS SDK we have an event emitter that we pass around that can be used for this kind of "onBlah" behavior I guess the sessionManager can have a Is there a more android-y way of doing it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah a callback would be one way, since |
||
val newSessionId = PostHogSessionManager.getActiveSessionId().toString() | ||
val newProps = buildProperties.toMutableMap() | ||
newProps["\$session_id"] = newSessionId | ||
newProps["\$window_id"] = newSessionId | ||
return newProps | ||
} | ||
return buildProperties | ||
} | ||
|
||
public override fun optIn() { | ||
if (!isEnabled()) { | ||
return | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,8 @@ import com.posthog.PostHogInternal | |
import com.posthog.vendor.uuid.TimeBasedEpochGenerator | ||
import java.util.UUID | ||
|
||
public const val MAX_SESSION_AGE_MILLIS: Int = 24 * 60 * 60 * 60 | ||
pauldambra marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can it be private instead? |
||
|
||
/** | ||
* Class that manages the Session ID | ||
*/ | ||
|
@@ -16,10 +18,15 @@ public object PostHogSessionManager { | |
|
||
private var sessionId = sessionIdNone | ||
|
||
public fun startSession() { | ||
public fun startSession(sessionStartTime: Long? = null) { | ||
synchronized(sessionLock) { | ||
if (sessionId == sessionIdNone) { | ||
sessionId = TimeBasedEpochGenerator.generate() | ||
sessionId = | ||
if (sessionStartTime != null) { | ||
TimeBasedEpochGenerator.generate(sessionStartTime) | ||
} else { | ||
TimeBasedEpochGenerator.generate() | ||
} | ||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,10 +29,10 @@ internal class FakePostHogDateProvider : PostHogDateProvider { | |
} | ||
|
||
override fun currentTimeMillis(): Long { | ||
return System.currentTimeMillis() | ||
return currentDate?.time ?: System.currentTimeMillis() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this threw me 🤣 |
||
} | ||
|
||
override fun nanoTime(): Long { | ||
return System.nanoTime() | ||
return currentDate?.time?.times(1000000) ?: System.nanoTime() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so I updated here too |
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will only be executed if an event is captured instead of when 24 hours have passed. Is that intentional? should we also do this eg when the app is put on foreground/backgorund?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i guess the foreground change is righ there
was assuming that we didn't want to run a timer just for this - although ultimately that's where JS SDK ended up
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we already have a timer when the app is put in the background, see here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe that timer should also call
enforceMaxSessionLength
when its back in the foreground so we don't rely on the next event but rather if the app is back visible to the userThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
me: "let's open a PR and see how much I don't know"
me: *regrets life choices
🤣