From d72878b5a4902b6b6d4c98a457b6beec2fbf9e37 Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Wed, 28 Oct 2026 19:46:51 -0400 Subject: [PATCH 1/9] chore(api): keep @PostHogInternal declarations out of the API dumps --- build.gradle.kts | 2 + posthog-android/api/posthog-android.api | 14 - posthog/api/posthog.api | 900 ------------------------ 3 files changed, 2 insertions(+), 914 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2d3d940ef..b44efdd53 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -66,6 +66,8 @@ apiValidation { ignoredProjects.add("posthog-android-sample") // Pre-1.0 (0.x) module — public surface may change between minor versions until 1.0.0. ignoredProjects.add("posthog-android-surveys-compose") + // APIs only wrapper SDKs and the SDK's own modules may call; not part of the public API. + nonPublicMarkers.add("com.posthog.PostHogInternal") } nexusPublishing.postHogConfig() diff --git a/posthog-android/api/posthog-android.api b/posthog-android/api/posthog-android.api index 1749b4128..59262210a 100644 --- a/posthog-android/api/posthog-android.api +++ b/posthog-android/api/posthog-android.api @@ -58,20 +58,6 @@ public final class com/posthog/android/errortracking/PostHogNativeCrashIntegrati public fun uninstall ()V } -public final class com/posthog/android/internal/MainHandler { - public fun ()V - public fun (Landroid/os/Looper;)V - public synthetic fun (Landroid/os/Looper;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getHandler ()Landroid/os/Handler; - public final fun getMainLooper ()Landroid/os/Looper; -} - -public final class com/posthog/android/internal/PostHogAndroidUtilsKt { - public static final fun base64 (Landroid/graphics/Bitmap;Landroid/graphics/Bitmap$CompressFormat;I)Ljava/lang/String; - public static synthetic fun base64$default (Landroid/graphics/Bitmap;Landroid/graphics/Bitmap$CompressFormat;IILjava/lang/Object;)Ljava/lang/String; - public static final fun getApplicationInfo (Landroid/content/Context;)Landroid/content/pm/ApplicationInfo; -} - public abstract interface class com/posthog/android/replay/PostHogDrawableConverter { public abstract fun convert (Landroid/graphics/drawable/Drawable;)Landroid/graphics/Bitmap; } diff --git a/posthog/api/posthog.api b/posthog/api/posthog.api index dd1ef6174..389bcc9e9 100644 --- a/posthog/api/posthog.api +++ b/posthog/api/posthog.api @@ -166,17 +166,13 @@ public class com/posthog/PostHogConfig { public synthetic fun (Ljava/lang/String;Ljava/lang/String;ZZZIZLjava/util/List;ZIIIIIILcom/posthog/PostHogEncryption;Lcom/posthog/PostHogFeatureFlagsCallback;ZLkotlin/jvm/functions/Function1;ZLcom/posthog/PersonProfiles;ZLjava/net/Proxy;Lcom/posthog/surveys/PostHogSurveysConfig;Lcom/posthog/logs/PostHogLogsConfig;Lkotlin/jvm/functions/Function6;Lkotlin/jvm/functions/Function5;Lcom/posthog/errortracking/PostHogErrorTrackingConfig;Ljava/lang/String;Lcom/posthog/PostHogBootstrapConfig;Lkotlin/jvm/functions/Function3;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun addBeforeSend (Lcom/posthog/PostHogBeforeSend;)V public final fun addIntegration (Lcom/posthog/PostHogIntegration;)V - public final fun getAiStoragePrefix ()Ljava/lang/String; public final fun getBeforeSendList ()Ljava/util/List; public final fun getBootstrap ()Lcom/posthog/PostHogBootstrapConfig; - public final fun getCachePreferences ()Lcom/posthog/internal/PostHogPreferences; public final fun getCaptureAiCompression ()Lcom/posthog/PostHogCompression; public final fun getCaptureAiMaxEventBytes ()I public final fun getCaptureAiMaxQueueSize ()I public final fun getCaptureAiTimeout ()I public final fun getCompression ()Lcom/posthog/PostHogCompression; - public final fun getContext ()Lcom/posthog/internal/PostHogContext; - public final fun getDateProvider ()Lcom/posthog/internal/PostHogDateProvider; public final fun getDebug ()Z public final fun getEncryption ()Lcom/posthog/PostHogEncryption; public final fun getErrorTrackingConfig ()Lcom/posthog/errortracking/PostHogErrorTrackingConfig; @@ -187,55 +183,36 @@ public class com/posthog/PostHogConfig { public final fun getFlushIntervalSeconds ()I public final fun getGetAnonymousId ()Lkotlin/jvm/functions/Function1; public final fun getHost ()Ljava/lang/String; - public final fun getHttpClient ()Lokhttp3/OkHttpClient; public final fun getIntegrations ()Ljava/util/List; - public final fun getLegacyStoragePrefix ()Ljava/lang/String; - public final fun getLogger ()Lcom/posthog/internal/PostHogLogger; public final fun getLogs ()Lcom/posthog/logs/PostHogLogsConfig; - public final fun getLogsStoragePrefix ()Ljava/lang/String; public final fun getMaxBatchSize ()I public final fun getMaxQueueSize ()I - public final fun getNetworkStatus ()Lcom/posthog/internal/PostHogNetworkStatus; public final fun getOnFeatureFlags ()Lcom/posthog/PostHogFeatureFlagsCallback; public final fun getOptOut ()Z - public final fun getPersistOptOut ()Z public final fun getPersonProfiles ()Lcom/posthog/PersonProfiles; public final fun getPreloadFeatureFlags ()Z public final fun getProjectToken ()Ljava/lang/String; public final fun getProxy ()Ljava/net/Proxy; public final fun getPushIdentityProvider ()Lkotlin/jvm/functions/Function3; public final fun getPushNotificationSubscriptionMaxRetries ()I - public final fun getQueueProvider ()Lkotlin/jvm/functions/Function5; public final fun getReleaseIdentifier ()Ljava/lang/String; - public final fun getRemoteConfigHolder ()Lcom/posthog/internal/PostHogRemoteConfig; public final fun getRemoteConfigProvider ()Lkotlin/jvm/functions/Function6; - public final fun getReplayStoragePrefix ()Ljava/lang/String; public final fun getRequestHeaders ()Ljava/util/Map; public final fun getReuseAnonymousId ()Z - public final fun getSampleRateProvider ()Lkotlin/jvm/functions/Function0; - public final fun getSdkName ()Ljava/lang/String; - public final fun getSdkVersion ()Ljava/lang/String; public final fun getSendFeatureFlagEvent ()Z - public final fun getSerializer ()Lcom/posthog/internal/PostHogSerializer; public final fun getSessionReplay ()Z public final fun getSetDefaultPersonProperties ()Z - public final fun getSnapshotEndpoint ()Ljava/lang/String; - public final fun getStoragePrefix ()Ljava/lang/String; public final fun getSurveys ()Z public final fun getSurveysConfig ()Lcom/posthog/surveys/PostHogSurveysConfig; public final synthetic fun getTracingHeaders ()Ljava/util/List; public final fun removeBeforeSend (Lcom/posthog/PostHogBeforeSend;)V public final fun removeIntegration (Lcom/posthog/PostHogIntegration;)V - public final fun setAiStoragePrefix (Ljava/lang/String;)V public final fun setBootstrap (Lcom/posthog/PostHogBootstrapConfig;)V - public final fun setCachePreferences (Lcom/posthog/internal/PostHogPreferences;)V public final fun setCaptureAiCompression (Lcom/posthog/PostHogCompression;)V public final fun setCaptureAiMaxEventBytes (I)V public final fun setCaptureAiMaxQueueSize (I)V public final fun setCaptureAiTimeout (I)V public final fun setCompression (Lcom/posthog/PostHogCompression;)V - public final fun setContext (Lcom/posthog/internal/PostHogContext;)V - public final fun setDateProvider (Lcom/posthog/internal/PostHogDateProvider;)V public final fun setDebug (Z)V public final fun setEncryption (Lcom/posthog/PostHogEncryption;)V public final fun setEvaluationContexts (Ljava/util/List;)V @@ -244,35 +221,22 @@ public class com/posthog/PostHogConfig { public final fun setFlushAt (I)V public final fun setFlushIntervalSeconds (I)V public final fun setGetAnonymousId (Lkotlin/jvm/functions/Function1;)V - public final fun setHttpClient (Lokhttp3/OkHttpClient;)V - public final fun setLegacyStoragePrefix (Ljava/lang/String;)V - public final fun setLogger (Lcom/posthog/internal/PostHogLogger;)V public final fun setLogs (Lcom/posthog/logs/PostHogLogsConfig;)V - public final fun setLogsStoragePrefix (Ljava/lang/String;)V public final fun setMaxBatchSize (I)V public final fun setMaxQueueSize (I)V - public final fun setNetworkStatus (Lcom/posthog/internal/PostHogNetworkStatus;)V public final fun setOnFeatureFlags (Lcom/posthog/PostHogFeatureFlagsCallback;)V public final fun setOptOut (Z)V - public final fun setPersistOptOut (Z)V public final fun setPersonProfiles (Lcom/posthog/PersonProfiles;)V public final fun setPreloadFeatureFlags (Z)V public final fun setProxy (Ljava/net/Proxy;)V public final fun setPushIdentityProvider (Lkotlin/jvm/functions/Function3;)V public final fun setPushNotificationSubscriptionMaxRetries (I)V public final fun setReleaseIdentifier (Ljava/lang/String;)V - public final fun setRemoteConfigHolder (Lcom/posthog/internal/PostHogRemoteConfig;)V - public final fun setReplayStoragePrefix (Ljava/lang/String;)V public final fun setRequestHeaders (Ljava/util/Map;)V public final fun setReuseAnonymousId (Z)V - public final fun setSampleRateProvider (Lkotlin/jvm/functions/Function0;)V - public final fun setSdkName (Ljava/lang/String;)V - public final fun setSdkVersion (Ljava/lang/String;)V public final fun setSendFeatureFlagEvent (Z)V public final fun setSessionReplay (Z)V public final fun setSetDefaultPersonProperties (Z)V - public final fun setSnapshotEndpoint (Ljava/lang/String;)V - public final fun setStoragePrefix (Ljava/lang/String;)V public final fun setSurveys (Z)V public final fun setSurveysConfig (Lcom/posthog/surveys/PostHogSurveysConfig;)V public final fun setTracingHeaders (Ljava/util/List;)V @@ -365,7 +329,6 @@ public abstract interface class com/posthog/PostHogFeatureFlagsCallback { } public final class com/posthog/PostHogFeatureFlagsLoaded { - public fun (Ljava/util/Map;Z)V public final fun getErrorsLoading ()Z public final fun getFlags ()Ljava/util/List; public final fun getVariants ()Ljava/util/Map; @@ -410,7 +373,6 @@ public abstract interface class com/posthog/PostHogInterface : com/posthog/PostH public abstract fun endSession ()V public abstract fun getAllFeatureFlags ()Ljava/util/List; public abstract fun getAnonymousId ()Ljava/lang/String; - public abstract fun getConfig ()Lcom/posthog/PostHogConfig; public abstract fun getDeviceId ()Ljava/lang/String; public abstract fun getFeatureFlag (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Boolean;)Ljava/lang/Object; public static synthetic fun getFeatureFlag$default (Lcom/posthog/PostHogInterface;Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Boolean;ILjava/lang/Object;)Ljava/lang/Object; @@ -501,11 +463,7 @@ public class com/posthog/PostHogStateless : com/posthog/PostHogStatelessInterfac public fun aliasStateless (Ljava/lang/String;Ljava/lang/String;)V protected final fun buildEvent (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Date;Ljava/util/Map;Ljava/util/UUID;)Lcom/posthog/PostHogEvent; public static synthetic fun buildEvent$default (Lcom/posthog/PostHogStateless;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Date;Ljava/util/Map;Ljava/util/UUID;ILjava/lang/Object;)Lcom/posthog/PostHogEvent; - protected final fun captureExceptionEvent (Ljava/lang/Throwable;Ljava/lang/String;Ljava/util/Map;Ljava/util/Date;Lkotlin/jvm/functions/Function0;)V public fun captureExceptionStateless (Ljava/lang/Throwable;Ljava/lang/String;Ljava/util/Map;)V - protected final fun captureFeatureFlagCalledEvent (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;)V - protected final fun captureFeatureFlagCalledEvent (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/util/Map;)V - public static synthetic fun captureFeatureFlagCalledEvent$default (Lcom/posthog/PostHogStateless;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)V public fun captureStateless (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Date;)V public fun close ()V public fun debug (Z)V @@ -597,19 +555,12 @@ public abstract interface annotation class com/posthog/PostHogVisibleForTesting public final class com/posthog/errortracking/PostHogErrorTrackingAutoCaptureIntegration : com/posthog/PostHogIntegration, java/lang/Thread$UncaughtExceptionHandler { public fun (Lcom/posthog/PostHogConfig;)V - public fun (Lcom/posthog/PostHogConfig;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)V public fun install (Lcom/posthog/PostHogInterface;)V - public final fun installWith (Lcom/posthog/errortracking/PostHogErrorTrackingAutoCaptureIntegration$CaptureTarget;)V public fun onRemoteConfig (Z)V public fun uncaughtException (Ljava/lang/Thread;Ljava/lang/Throwable;)V public fun uninstall ()V } -public abstract interface class com/posthog/errortracking/PostHogErrorTrackingAutoCaptureIntegration$CaptureTarget { - public abstract fun capture (Ljava/lang/Throwable;)V - public abstract fun flush ()V -} - public final class com/posthog/errortracking/PostHogErrorTrackingConfig { public fun ()V public fun (Z)V @@ -638,14 +589,6 @@ public final class com/posthog/errortracking/PostHogExceptionStepsConfig { public final fun setMaxBytes (I)V } -public final class com/posthog/internal/EndpointSpec { - public static final field Companion Lcom/posthog/internal/EndpointSpec$Companion; - public static final fun ai (Lcom/posthog/PostHogConfig;Lcom/posthog/internal/PostHogApi;Ljava/lang/String;)Lcom/posthog/internal/EndpointSpec; - public static final fun batch (Lcom/posthog/PostHogConfig;Lcom/posthog/internal/PostHogApi;Ljava/lang/String;)Lcom/posthog/internal/EndpointSpec; - public static final fun logs (Lcom/posthog/PostHogConfig;Lcom/posthog/internal/PostHogApi;Ljava/lang/String;)Lcom/posthog/internal/EndpointSpec; - public static final fun snapshot (Lcom/posthog/PostHogConfig;Lcom/posthog/internal/PostHogApi;Ljava/lang/String;)Lcom/posthog/internal/EndpointSpec; -} - public final class com/posthog/internal/EndpointSpec$Companion { public final fun ai (Lcom/posthog/PostHogConfig;Lcom/posthog/internal/PostHogApi;Ljava/lang/String;)Lcom/posthog/internal/EndpointSpec; public final fun batch (Lcom/posthog/PostHogConfig;Lcom/posthog/internal/PostHogApi;Ljava/lang/String;)Lcom/posthog/internal/EndpointSpec; @@ -653,304 +596,11 @@ public final class com/posthog/internal/EndpointSpec$Companion { public final fun snapshot (Lcom/posthog/PostHogConfig;Lcom/posthog/internal/PostHogApi;Ljava/lang/String;)Lcom/posthog/internal/EndpointSpec; } -public final class com/posthog/internal/EvaluationReason { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/Integer; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;)Lcom/posthog/internal/EvaluationReason; - public static synthetic fun copy$default (Lcom/posthog/internal/EvaluationReason;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;ILjava/lang/Object;)Lcom/posthog/internal/EvaluationReason; - public fun equals (Ljava/lang/Object;)Z - public final fun getCode ()Ljava/lang/String; - public final fun getCondition_index ()Ljava/lang/Integer; - public final fun getDescription ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/posthog/internal/FeatureFlag { - public fun (Ljava/lang/String;ZLjava/lang/String;Lcom/posthog/internal/FeatureFlagMetadata;Lcom/posthog/internal/EvaluationReason;Ljava/lang/Boolean;)V - public synthetic fun (Ljava/lang/String;ZLjava/lang/String;Lcom/posthog/internal/FeatureFlagMetadata;Lcom/posthog/internal/EvaluationReason;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Z - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Lcom/posthog/internal/FeatureFlagMetadata; - public final fun component5 ()Lcom/posthog/internal/EvaluationReason; - public final fun component6 ()Ljava/lang/Boolean; - public final fun copy (Ljava/lang/String;ZLjava/lang/String;Lcom/posthog/internal/FeatureFlagMetadata;Lcom/posthog/internal/EvaluationReason;Ljava/lang/Boolean;)Lcom/posthog/internal/FeatureFlag; - public static synthetic fun copy$default (Lcom/posthog/internal/FeatureFlag;Ljava/lang/String;ZLjava/lang/String;Lcom/posthog/internal/FeatureFlagMetadata;Lcom/posthog/internal/EvaluationReason;Ljava/lang/Boolean;ILjava/lang/Object;)Lcom/posthog/internal/FeatureFlag; - public fun equals (Ljava/lang/Object;)Z - public final fun getEnabled ()Z - public final fun getFailed ()Ljava/lang/Boolean; - public final fun getKey ()Ljava/lang/String; - public final fun getMetadata ()Lcom/posthog/internal/FeatureFlagMetadata; - public final fun getReason ()Lcom/posthog/internal/EvaluationReason; - public final fun getVariant ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/posthog/internal/FeatureFlagMetadata { - public fun (ILjava/lang/String;ILjava/lang/Boolean;Ljava/lang/String;)V - public synthetic fun (ILjava/lang/String;ILjava/lang/Boolean;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()I - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()I - public final fun component4 ()Ljava/lang/Boolean; - public final fun component5 ()Ljava/lang/String; - public final fun copy (ILjava/lang/String;ILjava/lang/Boolean;Ljava/lang/String;)Lcom/posthog/internal/FeatureFlagMetadata; - public static synthetic fun copy$default (Lcom/posthog/internal/FeatureFlagMetadata;ILjava/lang/String;ILjava/lang/Boolean;Ljava/lang/String;ILjava/lang/Object;)Lcom/posthog/internal/FeatureFlagMetadata; - public fun equals (Ljava/lang/Object;)Z - public final fun getEvaluationRuntime ()Ljava/lang/String; - public final fun getHasExperiment ()Ljava/lang/Boolean; - public final fun getId ()I - public final fun getPayload ()Ljava/lang/String; - public final fun getVersion ()I - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/posthog/internal/FlagConditionGroup { - public fun (Ljava/util/List;Ljava/lang/Double;Ljava/lang/String;)V - public final fun getProperties ()Ljava/util/List; - public final fun getRolloutPercentage ()Ljava/lang/Double; - public final fun getVariant ()Ljava/lang/String; -} - -public final class com/posthog/internal/FlagDefinition { - public fun (ILjava/lang/String;Ljava/lang/String;ZLcom/posthog/internal/FlagFilters;IZLjava/lang/Boolean;Ljava/lang/String;)V - public synthetic fun (ILjava/lang/String;Ljava/lang/String;ZLcom/posthog/internal/FlagFilters;IZLjava/lang/Boolean;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getActive ()Z - public final fun getEnsureExperienceContinuity ()Z - public final fun getEvaluationRuntime ()Ljava/lang/String; - public final fun getFilters ()Lcom/posthog/internal/FlagFilters; - public final fun getHasExperiment ()Ljava/lang/Boolean; - public final fun getId ()I - public final fun getKey ()Ljava/lang/String; - public final fun getName ()Ljava/lang/String; - public final fun getVersion ()I -} - -public final class com/posthog/internal/FlagFilters { - public fun (Ljava/util/List;Lcom/posthog/internal/MultiVariateConfig;Ljava/util/Map;Ljava/lang/Integer;)V - public final fun getAggregationGroupTypeIndex ()Ljava/lang/Integer; - public final fun getGroups ()Ljava/util/List; - public final fun getMultivariate ()Lcom/posthog/internal/MultiVariateConfig; - public final fun getPayloads ()Ljava/util/Map; -} - -public final class com/posthog/internal/FlagProperty { - public fun (Ljava/lang/String;Ljava/lang/Object;Lcom/posthog/internal/PropertyOperator;Lcom/posthog/internal/PropertyType;Ljava/lang/Boolean;Ljava/util/List;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/Object; - public final fun component3 ()Lcom/posthog/internal/PropertyOperator; - public final fun component4 ()Lcom/posthog/internal/PropertyType; - public final fun component5 ()Ljava/lang/Boolean; - public final fun component6 ()Ljava/util/List; - public final fun copy (Ljava/lang/String;Ljava/lang/Object;Lcom/posthog/internal/PropertyOperator;Lcom/posthog/internal/PropertyType;Ljava/lang/Boolean;Ljava/util/List;)Lcom/posthog/internal/FlagProperty; - public static synthetic fun copy$default (Lcom/posthog/internal/FlagProperty;Ljava/lang/String;Ljava/lang/Object;Lcom/posthog/internal/PropertyOperator;Lcom/posthog/internal/PropertyType;Ljava/lang/Boolean;Ljava/util/List;ILjava/lang/Object;)Lcom/posthog/internal/FlagProperty; - public fun equals (Ljava/lang/Object;)Z - public final fun getDependencyChain ()Ljava/util/List; - public final fun getKey ()Ljava/lang/String; - public final fun getNegation ()Ljava/lang/Boolean; - public final fun getPropertyOperator ()Lcom/posthog/internal/PropertyOperator; - public final fun getPropertyValue ()Ljava/lang/Object; - public final fun getType ()Lcom/posthog/internal/PropertyType; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/posthog/internal/GzipRequestInterceptor : okhttp3/Interceptor { - public fun (Lcom/posthog/PostHogConfig;)V - public fun (Lcom/posthog/PostHogConfig;Lkotlin/jvm/functions/Function0;)V - public synthetic fun (Lcom/posthog/PostHogConfig;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun intercept (Lokhttp3/Interceptor$Chain;)Lokhttp3/Response; -} - -public final class com/posthog/internal/LocalEvaluationApiResponse { - public static final field Companion Lcom/posthog/internal/LocalEvaluationApiResponse$Companion; - public synthetic fun (Lcom/posthog/internal/LocalEvaluationResponse;Ljava/lang/String;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getEtag ()Ljava/lang/String; - public final fun getResult ()Lcom/posthog/internal/LocalEvaluationResponse; - public final fun getWasModified ()Z -} - public final class com/posthog/internal/LocalEvaluationApiResponse$Companion { public final fun notModified (Ljava/lang/String;)Lcom/posthog/internal/LocalEvaluationApiResponse; public final fun success (Lcom/posthog/internal/LocalEvaluationResponse;Ljava/lang/String;)Lcom/posthog/internal/LocalEvaluationApiResponse; } -public final class com/posthog/internal/LocalEvaluationResponse { - public fun (Ljava/util/List;Ljava/util/Map;Ljava/util/Map;)V - public fun (Ljava/util/List;Ljava/util/Map;Ljava/util/Map;Ljava/lang/Integer;)V - public synthetic fun (Ljava/util/List;Ljava/util/Map;Ljava/util/Map;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/List; - public final fun component2 ()Ljava/util/Map; - public final fun component3 ()Ljava/util/Map; - public final fun component4 ()Ljava/lang/Integer; - public final fun copy (Ljava/util/List;Ljava/util/Map;Ljava/util/Map;Ljava/lang/Integer;)Lcom/posthog/internal/LocalEvaluationResponse; - public static synthetic fun copy$default (Lcom/posthog/internal/LocalEvaluationResponse;Ljava/util/List;Ljava/util/Map;Ljava/util/Map;Ljava/lang/Integer;ILjava/lang/Object;)Lcom/posthog/internal/LocalEvaluationResponse; - public fun equals (Ljava/lang/Object;)Z - public final fun getCohorts ()Ljava/util/Map; - public final fun getFlags ()Ljava/util/List; - public final fun getGroupTypeMapping ()Ljava/util/Map; - public final fun getPropertyMatchingVersion ()Ljava/lang/Integer; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/posthog/internal/LogicalOperator : java/lang/Enum { - public static final field AND Lcom/posthog/internal/LogicalOperator; - public static final field OR Lcom/posthog/internal/LogicalOperator; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public static fun valueOf (Ljava/lang/String;)Lcom/posthog/internal/LogicalOperator; - public static fun values ()[Lcom/posthog/internal/LogicalOperator; -} - -public final class com/posthog/internal/MultiVariateConfig { - public fun (Ljava/util/List;)V - public final fun component1 ()Ljava/util/List; - public final fun copy (Ljava/util/List;)Lcom/posthog/internal/MultiVariateConfig; - public static synthetic fun copy$default (Lcom/posthog/internal/MultiVariateConfig;Ljava/util/List;ILjava/lang/Object;)Lcom/posthog/internal/MultiVariateConfig; - public fun equals (Ljava/lang/Object;)Z - public final fun getVariants ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/posthog/internal/PostHogApi { - public fun (Lcom/posthog/PostHogConfig;)V - public final fun batch (Ljava/util/List;)V - public final fun flags (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/List;Z)Lcom/posthog/internal/PostHogFlagsResponse; - public static synthetic fun flags$default (Lcom/posthog/internal/PostHogApi;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/List;ZILjava/lang/Object;)Lcom/posthog/internal/PostHogFlagsResponse; - public final fun localEvaluation (Ljava/lang/String;Ljava/lang/String;)Lcom/posthog/internal/LocalEvaluationApiResponse; - public static synthetic fun localEvaluation$default (Lcom/posthog/internal/PostHogApi;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/posthog/internal/LocalEvaluationApiResponse; - public final fun pushSubscription (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public static synthetic fun pushSubscription$default (Lcom/posthog/internal/PostHogApi;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)V - public final fun pushUnsubscription (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public static synthetic fun pushUnsubscription$default (Lcom/posthog/internal/PostHogApi;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)V - public final fun remoteConfig ()Lcom/posthog/internal/PostHogRemoteConfigResponse; - public final fun snapshot (Ljava/util/List;)V -} - -public final class com/posthog/internal/PostHogApiEndpoint : java/lang/Enum { - public static final field BATCH Lcom/posthog/internal/PostHogApiEndpoint; - public static final field SNAPSHOT Lcom/posthog/internal/PostHogApiEndpoint; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public static fun valueOf (Ljava/lang/String;)Lcom/posthog/internal/PostHogApiEndpoint; - public static fun values ()[Lcom/posthog/internal/PostHogApiEndpoint; -} - -public final class com/posthog/internal/PostHogApiError : java/lang/RuntimeException { - public fun (ILjava/lang/String;Lokhttp3/ResponseBody;Ljava/lang/Integer;Ljava/lang/String;)V - public synthetic fun (ILjava/lang/String;Lokhttp3/ResponseBody;Ljava/lang/Integer;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getBody ()Lokhttp3/ResponseBody; - public final fun getErrorCode ()Ljava/lang/String; - public fun getMessage ()Ljava/lang/String; - public final fun getRetryAfterSeconds ()Ljava/lang/Integer; - public final fun getStatusCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract interface class com/posthog/internal/PostHogContext { - public abstract fun getDynamicContext ()Ljava/util/Map; - public abstract fun getSdkInfo ()Ljava/util/Map; - public abstract fun getStaticContext ()Ljava/util/Map; -} - -public abstract interface class com/posthog/internal/PostHogDateProvider { - public abstract fun addSecondsToCurrentDate (I)Ljava/util/Date; - public abstract fun currentDate ()Ljava/util/Date; - public abstract fun currentTimeMillis ()J - public abstract fun nanoTime ()J -} - -public final class com/posthog/internal/PostHogDateUtilsKt { - public static final fun formatISO8601Date (Ljava/util/Date;)Ljava/lang/String; - public static final fun parseISO8601Date (Ljava/lang/String;)Ljava/util/Date; -} - -public abstract interface class com/posthog/internal/PostHogDefaultPersonPropertiesProvider { - public abstract fun getDefaultPersonProperties ()Ljava/util/Map; -} - -public final class com/posthog/internal/PostHogDeviceDateProvider : com/posthog/internal/PostHogDateProvider { - public fun ()V - public fun addSecondsToCurrentDate (I)Ljava/util/Date; - public fun currentDate ()Ljava/util/Date; - public fun currentTimeMillis ()J - public fun nanoTime ()J -} - -public abstract interface class com/posthog/internal/PostHogFeatureFlagCalledProvider { - public abstract fun onFeatureFlagCalled (Ljava/lang/String;Ljava/lang/Object;)V -} - -public abstract interface class com/posthog/internal/PostHogFeatureFlagsInterface { - public abstract fun clear ()V - public abstract fun getEvaluatedAt (Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Ljava/lang/Long; - public static synthetic fun getEvaluatedAt$default (Lcom/posthog/internal/PostHogFeatureFlagsInterface;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Ljava/lang/Long; - public fun getFeatureFlagDetails (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Lcom/posthog/internal/FeatureFlag; - public static synthetic fun getFeatureFlagDetails$default (Lcom/posthog/internal/PostHogFeatureFlagsInterface;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Lcom/posthog/internal/FeatureFlag; - public fun getFeatureFlagError (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Ljava/lang/String; - public static synthetic fun getFeatureFlagError$default (Lcom/posthog/internal/PostHogFeatureFlagsInterface;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Ljava/lang/String; - public abstract fun getFeatureFlagResult (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Lcom/posthog/FeatureFlagResult; - public static synthetic fun getFeatureFlagResult$default (Lcom/posthog/internal/PostHogFeatureFlagsInterface;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Lcom/posthog/FeatureFlagResult; - public abstract fun getFeatureFlags (Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Ljava/util/Map; - public static synthetic fun getFeatureFlags$default (Lcom/posthog/internal/PostHogFeatureFlagsInterface;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Ljava/util/Map; - public abstract fun getRequestId (Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Ljava/lang/String; - public static synthetic fun getRequestId$default (Lcom/posthog/internal/PostHogFeatureFlagsInterface;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Ljava/lang/String; - public fun isMinimalFlagCalledEventsEnabled ()Z - public fun shutDown ()V -} - -public final class com/posthog/internal/PostHogFeatureFlagsInterface$DefaultImpls { - public static synthetic fun getEvaluatedAt$default (Lcom/posthog/internal/PostHogFeatureFlagsInterface;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Ljava/lang/Long; - public static fun getFeatureFlagDetails (Lcom/posthog/internal/PostHogFeatureFlagsInterface;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Lcom/posthog/internal/FeatureFlag; - public static synthetic fun getFeatureFlagDetails$default (Lcom/posthog/internal/PostHogFeatureFlagsInterface;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Lcom/posthog/internal/FeatureFlag; - public static fun getFeatureFlagError (Lcom/posthog/internal/PostHogFeatureFlagsInterface;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Ljava/lang/String; - public static synthetic fun getFeatureFlagError$default (Lcom/posthog/internal/PostHogFeatureFlagsInterface;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Ljava/lang/String; - public static synthetic fun getFeatureFlagResult$default (Lcom/posthog/internal/PostHogFeatureFlagsInterface;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Lcom/posthog/FeatureFlagResult; - public static synthetic fun getFeatureFlags$default (Lcom/posthog/internal/PostHogFeatureFlagsInterface;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Ljava/util/Map; - public static synthetic fun getRequestId$default (Lcom/posthog/internal/PostHogFeatureFlagsInterface;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Ljava/lang/String; - public static fun isMinimalFlagCalledEventsEnabled (Lcom/posthog/internal/PostHogFeatureFlagsInterface;)Z - public static fun shutDown (Lcom/posthog/internal/PostHogFeatureFlagsInterface;)V -} - -public final class com/posthog/internal/PostHogFlagsResponse : com/posthog/internal/PostHogRemoteConfigResponse { - public fun (ZLjava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Boolean;)V - public synthetic fun (ZLjava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Z - public final fun component2 ()Ljava/util/Map; - public final fun component3 ()Ljava/util/Map; - public final fun component4 ()Ljava/util/Map; - public final fun component5 ()Ljava/lang/String; - public final fun component6 ()Ljava/lang/Long; - public final fun component7 ()Ljava/lang/Boolean; - public final fun copy (ZLjava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Boolean;)Lcom/posthog/internal/PostHogFlagsResponse; - public static synthetic fun copy$default (Lcom/posthog/internal/PostHogFlagsResponse;ZLjava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Boolean;ILjava/lang/Object;)Lcom/posthog/internal/PostHogFlagsResponse; - public fun equals (Ljava/lang/Object;)Z - public final fun getErrorsWhileComputingFlags ()Z - public final fun getEvaluatedAt ()Ljava/lang/Long; - public final fun getFeatureFlagPayloads ()Ljava/util/Map; - public final fun getFeatureFlags ()Ljava/util/Map; - public final fun getFlags ()Ljava/util/Map; - public final fun getMinimalFlagCalledEvents ()Ljava/lang/Boolean; - public final fun getRequestId ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract interface class com/posthog/internal/PostHogLogger { - public abstract fun isEnabled ()Z - public abstract fun log (Ljava/lang/String;)V - public fun logWarning (Ljava/lang/String;)V -} - -public final class com/posthog/internal/PostHogLogger$DefaultImpls { - public static fun logWarning (Lcom/posthog/internal/PostHogLogger;Ljava/lang/String;)V -} - public final class com/posthog/internal/PostHogMemoryPreferences : com/posthog/internal/PostHogPreferences { public fun ()V public fun clear (Ljava/util/List;)V @@ -960,50 +610,6 @@ public final class com/posthog/internal/PostHogMemoryPreferences : com/posthog/i public fun setValue (Ljava/lang/String;Ljava/lang/Object;)V } -public abstract interface class com/posthog/internal/PostHogNetworkStatus { - public abstract fun isConnected ()Z - public fun register (Lkotlin/jvm/functions/Function0;)V - public fun unregister ()V -} - -public final class com/posthog/internal/PostHogNetworkStatus$DefaultImpls { - public static fun register (Lcom/posthog/internal/PostHogNetworkStatus;Lkotlin/jvm/functions/Function0;)V - public static fun unregister (Lcom/posthog/internal/PostHogNetworkStatus;)V -} - -public final class com/posthog/internal/PostHogNoOpLogger : com/posthog/internal/PostHogLogger { - public fun ()V - public fun isEnabled ()Z - public fun log (Ljava/lang/String;)V -} - -public abstract interface class com/posthog/internal/PostHogOnRemoteConfigLoaded { - public abstract fun loaded ()V -} - -public abstract interface class com/posthog/internal/PostHogPreferences { - public static final field ANONYMOUS_ID Ljava/lang/String; - public static final field BUILD Ljava/lang/String; - public static final field Companion Lcom/posthog/internal/PostHogPreferences$Companion; - public static final field DEVICE_ID Ljava/lang/String; - public static final field DISTINCT_ID Ljava/lang/String; - public static final field GROUPS Ljava/lang/String; - public static final field LAST_SEEN_SURVEY_DATE Ljava/lang/String; - public static final field PUSH_OPENED_MESSAGE_IDS Ljava/lang/String; - public static final field STRINGIFIED_KEYS Ljava/lang/String; - public static final field SURVEY_PROGRESS Ljava/lang/String; - public static final field SURVEY_SEEN Ljava/lang/String; - public static final field VERSION Ljava/lang/String; - public abstract fun clear (Ljava/util/List;)V - public static synthetic fun clear$default (Lcom/posthog/internal/PostHogPreferences;Ljava/util/List;ILjava/lang/Object;)V - public abstract fun getAll ()Ljava/util/Map; - public abstract fun getValue (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object; - public static synthetic fun getValue$default (Lcom/posthog/internal/PostHogPreferences;Ljava/lang/String;Ljava/lang/Object;ILjava/lang/Object;)Ljava/lang/Object; - public fun isAvailable ()Z - public abstract fun remove (Ljava/lang/String;)V - public abstract fun setValue (Ljava/lang/String;Ljava/lang/Object;)V -} - public final class com/posthog/internal/PostHogPreferences$Companion { public static final field ANONYMOUS_ID Ljava/lang/String; public static final field BUILD Ljava/lang/String; @@ -1011,7 +617,6 @@ public final class com/posthog/internal/PostHogPreferences$Companion { public static final field DISTINCT_ID Ljava/lang/String; public static final field GROUPS Ljava/lang/String; public static final field LAST_SEEN_SURVEY_DATE Ljava/lang/String; - public static final field PUSH_OPENED_MESSAGE_IDS Ljava/lang/String; public static final field STRINGIFIED_KEYS Ljava/lang/String; public static final field SURVEY_PROGRESS Ljava/lang/String; public static final field SURVEY_SEEN Ljava/lang/String; @@ -1019,81 +624,6 @@ public final class com/posthog/internal/PostHogPreferences$Companion { public final fun getALL_INTERNAL_KEYS ()Ljava/util/Set; } -public final class com/posthog/internal/PostHogPreferences$DefaultImpls { - public static synthetic fun clear$default (Lcom/posthog/internal/PostHogPreferences;Ljava/util/List;ILjava/lang/Object;)V - public static synthetic fun getValue$default (Lcom/posthog/internal/PostHogPreferences;Ljava/lang/String;Ljava/lang/Object;ILjava/lang/Object;)Ljava/lang/Object; - public static fun isAvailable (Lcom/posthog/internal/PostHogPreferences;)Z -} - -public final class com/posthog/internal/PostHogQueue : com/posthog/internal/PostHogQueueInterface { - public fun (Lcom/posthog/PostHogConfig;Lcom/posthog/internal/EndpointSpec;Ljava/util/concurrent/ExecutorService;)V - public fun add (Ljava/lang/Object;)V - public fun clear ()V - public fun flush ()V - public final fun getQueueDirectory ()Ljava/io/File; - public fun getSize ()I - public final fun reloadFromDisk ()V - public fun start ()V - public fun stop ()V -} - -public abstract interface class com/posthog/internal/PostHogQueueInterface { - public abstract fun add (Ljava/lang/Object;)V - public abstract fun clear ()V - public abstract fun flush ()V - public fun flushForShutdown ()V - public abstract fun getSize ()I - public abstract fun start ()V - public abstract fun stop ()V -} - -public final class com/posthog/internal/PostHogQueueInterface$DefaultImpls { - public static fun flushForShutdown (Lcom/posthog/internal/PostHogQueueInterface;)V -} - -public final class com/posthog/internal/PostHogRemoteConfig : com/posthog/internal/PostHogFeatureFlagsInterface { - public fun (Lcom/posthog/PostHogConfig;Lcom/posthog/internal/PostHogApi;Ljava/util/concurrent/ExecutorService;Lcom/posthog/internal/PostHogDefaultPersonPropertiesProvider;Lcom/posthog/internal/PostHogFeatureFlagCalledProvider;Lcom/posthog/internal/PostHogOnRemoteConfigLoaded;)V - public synthetic fun (Lcom/posthog/PostHogConfig;Lcom/posthog/internal/PostHogApi;Ljava/util/concurrent/ExecutorService;Lcom/posthog/internal/PostHogDefaultPersonPropertiesProvider;Lcom/posthog/internal/PostHogFeatureFlagCalledProvider;Lcom/posthog/internal/PostHogOnRemoteConfigLoaded;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun clear ()V - public final fun consumeNewlyRegisterablePushAppIds ()Ljava/util/Set; - public final fun getActiveFeatureFlagKeys ()Ljava/util/List; - public fun getEvaluatedAt (Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Ljava/lang/Long; - public final fun getEventTriggers ()Ljava/util/Set; - public final fun getFeatureFlag (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Ljava/lang/Object; - public static synthetic fun getFeatureFlag$default (Lcom/posthog/internal/PostHogRemoteConfig;Ljava/lang/String;Ljava/lang/Object;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Ljava/lang/Object; - public final fun getFeatureFlagPayload (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Ljava/lang/Object; - public static synthetic fun getFeatureFlagPayload$default (Lcom/posthog/internal/PostHogRemoteConfig;Ljava/lang/String;Ljava/lang/Object;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Ljava/lang/Object; - public fun getFeatureFlagResult (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Lcom/posthog/FeatureFlagResult; - public fun getFeatureFlags (Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Ljava/util/Map; - public final fun getFlagDetails (Ljava/lang/String;)Lcom/posthog/internal/FeatureFlag; - public final fun getPersonPropertiesForFlags ()Ljava/util/Map; - public final fun getPushAppIds ()Ljava/util/List; - public final fun getRecordingMinimumDurationMs ()Ljava/lang/Long; - public fun getRequestId (Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Ljava/lang/String; - public final fun getSessionRecordingSampleRate ()Ljava/lang/Double; - public final fun getSurveys ()Ljava/util/List; - public final fun hasCachedErrorTrackingConfig ()Z - public final fun hasRemoteConfigFetched ()Z - public final fun isAutocaptureExceptionsEnabled ()Z - public final fun isCaptureNetworkTimingEnabled ()Z - public final fun isConsoleLogRecordingEnabled ()Z - public fun isMinimalFlagCalledEventsEnabled ()Z - public final fun isNativeCrashCaptureEnabled ()Z - public final fun isSessionReplayFlagActive ()Z - public final fun loadFeatureFlags (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Lkotlin/jvm/functions/Function0;Lcom/posthog/PostHogFeatureFlagsCallback;)V - public static synthetic fun loadFeatureFlags$default (Lcom/posthog/internal/PostHogRemoteConfig;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Lkotlin/jvm/functions/Function0;Lcom/posthog/PostHogFeatureFlagsCallback;ILjava/lang/Object;)V - public final fun loadRemoteConfig (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Lkotlin/jvm/functions/Function0;Lcom/posthog/PostHogFeatureFlagsCallback;)V - public static synthetic fun loadRemoteConfig$default (Lcom/posthog/internal/PostHogRemoteConfig;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Lkotlin/jvm/functions/Function0;Lcom/posthog/PostHogFeatureFlagsCallback;ILjava/lang/Object;)V - public final fun makeSamplingDecision (Ljava/lang/String;)Z - public final fun persistPushConfigCache ()V - public final fun resetGroupPropertiesForFlags (Ljava/lang/String;)V - public static synthetic fun resetGroupPropertiesForFlags$default (Lcom/posthog/internal/PostHogRemoteConfig;Ljava/lang/String;ILjava/lang/Object;)V - public final fun resetPersonPropertiesForFlags ()V - public final fun sessionReplayLinkedFlagSnapshot ()Lcom/posthog/internal/PostHogRemoteConfig$SessionReplayLinkedFlagSnapshot; - public final fun setGroupPropertiesForFlags (Ljava/lang/String;Ljava/util/Map;)V - public final fun setPersonPropertiesForFlags (Ljava/util/Map;)V -} - public final class com/posthog/internal/PostHogRemoteConfig$SessionReplayLinkedFlagSnapshot { public fun (ZZ)V public final fun component1 ()Z @@ -1107,129 +637,16 @@ public final class com/posthog/internal/PostHogRemoteConfig$SessionReplayLinkedF public fun toString ()Ljava/lang/String; } -public class com/posthog/internal/PostHogRemoteConfigResponse { - public fun ()V - public fun (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Boolean;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/util/List;)V - public synthetic fun (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Boolean;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getCapturePerformance ()Ljava/lang/Object; - public final fun getErrorTracking ()Ljava/lang/Object; - public final fun getHasFeatureFlags ()Ljava/lang/Boolean; - public final fun getPush ()Ljava/lang/Object; - public final fun getQuotaLimited ()Ljava/util/List; - public final fun getSessionRecording ()Ljava/lang/Object; - public final fun getSurveys ()Ljava/lang/Object; -} - -public final class com/posthog/internal/PostHogSerializer { - public fun (Lcom/posthog/PostHogConfig;)V - public final fun deserializeString (Ljava/lang/String;)Ljava/lang/Object; - public final fun getGson ()Lcom/google/gson/Gson; - public final fun serializeObject (Ljava/lang/Object;)Ljava/lang/String; -} - -public final class com/posthog/internal/PostHogSessionManager { - public static final field INSTANCE Lcom/posthog/internal/PostHogSessionManager; - public final fun endSession ()V - public final fun getActiveSessionId ()Ljava/util/UUID; - public final fun isReactNative ()Z - public final fun isSessionActive ()Z - public final fun isSessionExceedingMaxDuration (J)Z - public final fun peekSessionId ()Ljava/util/UUID; - public final fun setAppInBackground (Z)V - public final fun setDateProvider (Lcom/posthog/internal/PostHogDateProvider;)V - public final fun setReactNative (Z)V - public final fun setSessionId (Ljava/util/UUID;)V - public final fun startSession ()V - public final fun touchSession ()V -} - -public final class com/posthog/internal/PostHogThreadFactory : java/util/concurrent/ThreadFactory { - public fun (Ljava/lang/String;)V - public fun newThread (Ljava/lang/Runnable;)Ljava/lang/Thread; -} - -public final class com/posthog/internal/PostHogUtilsKt { - public static final fun executeSafely (Ljava/util/concurrent/Executor;Ljava/lang/Runnable;)V - public static final fun interruptSafely (Ljava/lang/Thread;)V - public static final fun isNetworkingError (Ljava/lang/Throwable;)Z - public static final fun logIntegrationFailure (Lcom/posthog/PostHogConfig;Ljava/lang/String;Ljava/lang/Throwable;)V - public static final fun submitSyncSafely (Ljava/util/concurrent/ExecutorService;Ljava/lang/Runnable;)V -} - -public final class com/posthog/internal/PropertyGroup { - public fun (Lcom/posthog/internal/LogicalOperator;Lcom/posthog/internal/PropertyValue;)V - public final fun component1 ()Lcom/posthog/internal/LogicalOperator; - public final fun component2 ()Lcom/posthog/internal/PropertyValue; - public final fun copy (Lcom/posthog/internal/LogicalOperator;Lcom/posthog/internal/PropertyValue;)Lcom/posthog/internal/PropertyGroup; - public static synthetic fun copy$default (Lcom/posthog/internal/PropertyGroup;Lcom/posthog/internal/LogicalOperator;Lcom/posthog/internal/PropertyValue;ILjava/lang/Object;)Lcom/posthog/internal/PropertyGroup; - public fun equals (Ljava/lang/Object;)Z - public final fun getType ()Lcom/posthog/internal/LogicalOperator; - public final fun getValues ()Lcom/posthog/internal/PropertyValue; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/posthog/internal/PropertyOperator : java/lang/Enum { - public static final field Companion Lcom/posthog/internal/PropertyOperator$Companion; - public static final field ENDS_WITH Lcom/posthog/internal/PropertyOperator; - public static final field EXACT Lcom/posthog/internal/PropertyOperator; - public static final field FLAG_EVALUATES_TO Lcom/posthog/internal/PropertyOperator; - public static final field GT Lcom/posthog/internal/PropertyOperator; - public static final field GTE Lcom/posthog/internal/PropertyOperator; - public static final field ICONTAINS Lcom/posthog/internal/PropertyOperator; - public static final field IN Lcom/posthog/internal/PropertyOperator; - public static final field IS_DATE_AFTER Lcom/posthog/internal/PropertyOperator; - public static final field IS_DATE_BEFORE Lcom/posthog/internal/PropertyOperator; - public static final field IS_NOT Lcom/posthog/internal/PropertyOperator; - public static final field IS_NOT_SET Lcom/posthog/internal/PropertyOperator; - public static final field IS_SET Lcom/posthog/internal/PropertyOperator; - public static final field LT Lcom/posthog/internal/PropertyOperator; - public static final field LTE Lcom/posthog/internal/PropertyOperator; - public static final field NOT_ENDS_WITH Lcom/posthog/internal/PropertyOperator; - public static final field NOT_ICONTAINS Lcom/posthog/internal/PropertyOperator; - public static final field NOT_REGEX Lcom/posthog/internal/PropertyOperator; - public static final field NOT_STARTS_WITH Lcom/posthog/internal/PropertyOperator; - public static final field REGEX Lcom/posthog/internal/PropertyOperator; - public static final field SEMVER_CARET Lcom/posthog/internal/PropertyOperator; - public static final field SEMVER_EQ Lcom/posthog/internal/PropertyOperator; - public static final field SEMVER_GT Lcom/posthog/internal/PropertyOperator; - public static final field SEMVER_GTE Lcom/posthog/internal/PropertyOperator; - public static final field SEMVER_LT Lcom/posthog/internal/PropertyOperator; - public static final field SEMVER_LTE Lcom/posthog/internal/PropertyOperator; - public static final field SEMVER_NEQ Lcom/posthog/internal/PropertyOperator; - public static final field SEMVER_TILDE Lcom/posthog/internal/PropertyOperator; - public static final field SEMVER_WILDCARD Lcom/posthog/internal/PropertyOperator; - public static final field STARTS_WITH Lcom/posthog/internal/PropertyOperator; - public static final field UNKNOWN Lcom/posthog/internal/PropertyOperator; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public static fun valueOf (Ljava/lang/String;)Lcom/posthog/internal/PropertyOperator; - public static fun values ()[Lcom/posthog/internal/PropertyOperator; -} - public final class com/posthog/internal/PropertyOperator$Companion { public final fun fromString (Ljava/lang/String;)Lcom/posthog/internal/PropertyOperator; public final fun fromStringOrNull (Ljava/lang/String;)Lcom/posthog/internal/PropertyOperator; } -public final class com/posthog/internal/PropertyType : java/lang/Enum { - public static final field COHORT Lcom/posthog/internal/PropertyType; - public static final field Companion Lcom/posthog/internal/PropertyType$Companion; - public static final field FLAG Lcom/posthog/internal/PropertyType; - public static final field PERSON Lcom/posthog/internal/PropertyType; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public static fun valueOf (Ljava/lang/String;)Lcom/posthog/internal/PropertyType; - public static fun values ()[Lcom/posthog/internal/PropertyType; -} - public final class com/posthog/internal/PropertyType$Companion { public final fun fromString (Ljava/lang/String;)Lcom/posthog/internal/PropertyType; public final fun fromStringOrNull (Ljava/lang/String;)Lcom/posthog/internal/PropertyType; } -public abstract interface class com/posthog/internal/PropertyValue { - public abstract fun isEmpty ()Z -} - public final class com/posthog/internal/PropertyValue$FlagProperties : com/posthog/internal/PropertyValue { public fun (Ljava/util/List;)V public final fun component1 ()Ljava/util/List; @@ -1254,153 +671,14 @@ public final class com/posthog/internal/PropertyValue$PropertyGroups : com/posth public fun toString ()Ljava/lang/String; } -public final class com/posthog/internal/VariantDefinition { - public fun (Ljava/lang/String;D)V - public final fun getKey ()Ljava/lang/String; - public final fun getRolloutPercentage ()D -} - -public final class com/posthog/internal/errortracking/ThrowableCoercer { - public static final field EXCEPTION_LEVEL_ATTRIBUTE Ljava/lang/String; - public static final field EXCEPTION_LEVEL_FATAL Ljava/lang/String; - public static final field MAX_EXCEPTION_LIST_SIZE I - public static final field MAX_FRAMES_PER_STACKTRACE I - public fun ()V -} - -public abstract interface class com/posthog/internal/replay/PostHogSessionReplayHandler { - public abstract fun debugProperties ()Ljava/util/Map; - public abstract fun isActive ()Z - public abstract fun onEvent (Ljava/lang/String;Ljava/util/Map;)V - public static synthetic fun onEvent$default (Lcom/posthog/internal/replay/PostHogSessionReplayHandler;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)V - public abstract fun onSessionIdChanged ()V - public abstract fun start (Z)V - public abstract fun stop ()V -} - -public final class com/posthog/internal/replay/PostHogSessionReplayHandler$DefaultImpls { - public static synthetic fun onEvent$default (Lcom/posthog/internal/replay/PostHogSessionReplayHandler;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)V -} - -public final class com/posthog/internal/replay/RRCustomEvent : com/posthog/internal/replay/RREvent { - public fun (Ljava/lang/String;Ljava/lang/Object;J)V -} - -public class com/posthog/internal/replay/RREvent { - public fun (Lcom/posthog/internal/replay/RREventType;JLjava/lang/Object;)V - public synthetic fun (Lcom/posthog/internal/replay/RREventType;JLjava/lang/Object;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getData ()Ljava/lang/Object; - public final fun getTimestamp ()J - public final fun getType ()Lcom/posthog/internal/replay/RREventType; -} - -public final class com/posthog/internal/replay/RREventType : java/lang/Enum { - public static final field Companion Lcom/posthog/internal/replay/RREventType$Companion; - public static final field Custom Lcom/posthog/internal/replay/RREventType; - public static final field DomContentLoaded Lcom/posthog/internal/replay/RREventType; - public static final field FullSnapshot Lcom/posthog/internal/replay/RREventType; - public static final field IncrementalSnapshot Lcom/posthog/internal/replay/RREventType; - public static final field Load Lcom/posthog/internal/replay/RREventType; - public static final field Meta Lcom/posthog/internal/replay/RREventType; - public static final field Plugin Lcom/posthog/internal/replay/RREventType; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public final fun getValue ()I - public static fun valueOf (Ljava/lang/String;)Lcom/posthog/internal/replay/RREventType; - public static fun values ()[Lcom/posthog/internal/replay/RREventType; -} - public final class com/posthog/internal/replay/RREventType$Companion { public final fun fromValue (I)Lcom/posthog/internal/replay/RREventType; } -public final class com/posthog/internal/replay/RRFullSnapshotEvent : com/posthog/internal/replay/RREvent { - public fun (Ljava/util/List;IIJ)V -} - -public final class com/posthog/internal/replay/RRIncrementalMouseInteractionData { - public fun (ILcom/posthog/internal/replay/RRMouseInteraction;IILcom/posthog/internal/replay/RRIncrementalSource;ILjava/util/List;)V - public synthetic fun (ILcom/posthog/internal/replay/RRMouseInteraction;IILcom/posthog/internal/replay/RRIncrementalSource;ILjava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getId ()I - public final fun getPointerType ()I - public final fun getPositions ()Ljava/util/List; - public final fun getSource ()Lcom/posthog/internal/replay/RRIncrementalSource; - public final fun getType ()Lcom/posthog/internal/replay/RRMouseInteraction; - public final fun getX ()I - public final fun getY ()I -} - -public final class com/posthog/internal/replay/RRIncrementalMouseInteractionEvent : com/posthog/internal/replay/RREvent { - public fun (Lcom/posthog/internal/replay/RRIncrementalMouseInteractionData;J)V - public synthetic fun (Lcom/posthog/internal/replay/RRIncrementalMouseInteractionData;JILkotlin/jvm/internal/DefaultConstructorMarker;)V -} - -public final class com/posthog/internal/replay/RRIncrementalMutationData { - public fun ()V - public fun (Ljava/util/List;Ljava/util/List;Ljava/util/List;Lcom/posthog/internal/replay/RRIncrementalSource;)V - public synthetic fun (Ljava/util/List;Ljava/util/List;Ljava/util/List;Lcom/posthog/internal/replay/RRIncrementalSource;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getAdds ()Ljava/util/List; - public final fun getRemoves ()Ljava/util/List; - public final fun getSource ()Lcom/posthog/internal/replay/RRIncrementalSource; - public final fun getUpdates ()Ljava/util/List; -} - -public final class com/posthog/internal/replay/RRIncrementalSnapshotEvent : com/posthog/internal/replay/RREvent { - public fun (Lcom/posthog/internal/replay/RRIncrementalMutationData;J)V - public synthetic fun (Lcom/posthog/internal/replay/RRIncrementalMutationData;JILkotlin/jvm/internal/DefaultConstructorMarker;)V -} - -public final class com/posthog/internal/replay/RRIncrementalSource : java/lang/Enum { - public static final field AdoptedStyleSheet Lcom/posthog/internal/replay/RRIncrementalSource; - public static final field CanvasMutation Lcom/posthog/internal/replay/RRIncrementalSource; - public static final field Companion Lcom/posthog/internal/replay/RRIncrementalSource$Companion; - public static final field CustomElement Lcom/posthog/internal/replay/RRIncrementalSource; - public static final field Drag Lcom/posthog/internal/replay/RRIncrementalSource; - public static final field Font Lcom/posthog/internal/replay/RRIncrementalSource; - public static final field Input Lcom/posthog/internal/replay/RRIncrementalSource; - public static final field Log Lcom/posthog/internal/replay/RRIncrementalSource; - public static final field MediaInteraction Lcom/posthog/internal/replay/RRIncrementalSource; - public static final field MouseInteraction Lcom/posthog/internal/replay/RRIncrementalSource; - public static final field MouseMove Lcom/posthog/internal/replay/RRIncrementalSource; - public static final field Mutation Lcom/posthog/internal/replay/RRIncrementalSource; - public static final field Scroll Lcom/posthog/internal/replay/RRIncrementalSource; - public static final field Selection Lcom/posthog/internal/replay/RRIncrementalSource; - public static final field StyleDeclaration Lcom/posthog/internal/replay/RRIncrementalSource; - public static final field StyleSheetRule Lcom/posthog/internal/replay/RRIncrementalSource; - public static final field TouchMove Lcom/posthog/internal/replay/RRIncrementalSource; - public static final field ViewportResize Lcom/posthog/internal/replay/RRIncrementalSource; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public final fun getValue ()I - public static fun valueOf (Ljava/lang/String;)Lcom/posthog/internal/replay/RRIncrementalSource; - public static fun values ()[Lcom/posthog/internal/replay/RRIncrementalSource; -} - public final class com/posthog/internal/replay/RRIncrementalSource$Companion { public final fun fromValue (I)Lcom/posthog/internal/replay/RRIncrementalSource; } -public final class com/posthog/internal/replay/RRMetaEvent : com/posthog/internal/replay/RREvent { - public fun (IIJLjava/lang/String;)V -} - -public final class com/posthog/internal/replay/RRMouseInteraction : java/lang/Enum { - public static final field Blur Lcom/posthog/internal/replay/RRMouseInteraction; - public static final field Click Lcom/posthog/internal/replay/RRMouseInteraction; - public static final field Companion Lcom/posthog/internal/replay/RRMouseInteraction$Companion; - public static final field ContextMenu Lcom/posthog/internal/replay/RRMouseInteraction; - public static final field DblClick Lcom/posthog/internal/replay/RRMouseInteraction; - public static final field Focus Lcom/posthog/internal/replay/RRMouseInteraction; - public static final field MouseDown Lcom/posthog/internal/replay/RRMouseInteraction; - public static final field MouseUp Lcom/posthog/internal/replay/RRMouseInteraction; - public static final field TouchCancel Lcom/posthog/internal/replay/RRMouseInteraction; - public static final field TouchEnd Lcom/posthog/internal/replay/RRMouseInteraction; - public static final field TouchMoveDeparted Lcom/posthog/internal/replay/RRMouseInteraction; - public static final field TouchStart Lcom/posthog/internal/replay/RRMouseInteraction; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public final fun getValue ()I - public static fun valueOf (Ljava/lang/String;)Lcom/posthog/internal/replay/RRMouseInteraction; - public static fun values ()[Lcom/posthog/internal/replay/RRMouseInteraction; -} - public final class com/posthog/internal/replay/RRMouseInteraction$Companion { public final fun fromValue (I)Lcom/posthog/internal/replay/RRMouseInteraction; } @@ -1414,178 +692,6 @@ public final class com/posthog/internal/replay/RRMousePosition { public final fun getY ()I } -public final class com/posthog/internal/replay/RRMutatedNode { - public fun (Lcom/posthog/internal/replay/RRWireframe;Ljava/lang/Integer;)V - public synthetic fun (Lcom/posthog/internal/replay/RRWireframe;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getParentId ()Ljava/lang/Integer; - public final fun getWireframe ()Lcom/posthog/internal/replay/RRWireframe; -} - -public final class com/posthog/internal/replay/RRPluginEvent : com/posthog/internal/replay/RREvent { - public fun (Ljava/lang/String;Ljava/util/Map;J)V -} - -public final class com/posthog/internal/replay/RRRemovedNode { - public fun (ILjava/lang/Integer;)V - public synthetic fun (ILjava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getId ()I - public final fun getParentId ()Ljava/lang/Integer; -} - -public final class com/posthog/internal/replay/RRStyle { - public fun ()V - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component10 ()Ljava/lang/String; - public final fun component11 ()Ljava/lang/Integer; - public final fun component12 ()Ljava/lang/Integer; - public final fun component13 ()Ljava/lang/Integer; - public final fun component14 ()Ljava/lang/Integer; - public final fun component15 ()Ljava/lang/String; - public final fun component16 ()Ljava/lang/String; - public final fun component17 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/Integer; - public final fun component5 ()Ljava/lang/Integer; - public final fun component6 ()Ljava/lang/String; - public final fun component7 ()Ljava/lang/Integer; - public final fun component8 ()Ljava/lang/String; - public final fun component9 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/posthog/internal/replay/RRStyle; - public static synthetic fun copy$default (Lcom/posthog/internal/replay/RRStyle;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/posthog/internal/replay/RRStyle; - public fun equals (Ljava/lang/Object;)Z - public final fun getBackgroundColor ()Ljava/lang/String; - public final fun getBackgroundImage ()Ljava/lang/String; - public final fun getBar ()Ljava/lang/String; - public final fun getBorderColor ()Ljava/lang/String; - public final fun getBorderRadius ()Ljava/lang/Integer; - public final fun getBorderWidth ()Ljava/lang/Integer; - public final fun getColor ()Ljava/lang/String; - public final fun getFontFamily ()Ljava/lang/String; - public final fun getFontSize ()Ljava/lang/Integer; - public final fun getHorizontalAlign ()Ljava/lang/String; - public final fun getIconLeft ()Ljava/lang/String; - public final fun getIconRight ()Ljava/lang/String; - public final fun getPaddingBottom ()Ljava/lang/Integer; - public final fun getPaddingLeft ()Ljava/lang/Integer; - public final fun getPaddingRight ()Ljava/lang/Integer; - public final fun getPaddingTop ()Ljava/lang/Integer; - public final fun getVerticalAlign ()Ljava/lang/String; - public fun hashCode ()I - public final fun setBackgroundColor (Ljava/lang/String;)V - public final fun setBackgroundImage (Ljava/lang/String;)V - public final fun setBar (Ljava/lang/String;)V - public final fun setBorderColor (Ljava/lang/String;)V - public final fun setBorderRadius (Ljava/lang/Integer;)V - public final fun setBorderWidth (Ljava/lang/Integer;)V - public final fun setColor (Ljava/lang/String;)V - public final fun setFontFamily (Ljava/lang/String;)V - public final fun setFontSize (Ljava/lang/Integer;)V - public final fun setHorizontalAlign (Ljava/lang/String;)V - public final fun setIconLeft (Ljava/lang/String;)V - public final fun setIconRight (Ljava/lang/String;)V - public final fun setPaddingBottom (Ljava/lang/Integer;)V - public final fun setPaddingLeft (Ljava/lang/Integer;)V - public final fun setPaddingRight (Ljava/lang/Integer;)V - public final fun setPaddingTop (Ljava/lang/Integer;)V - public final fun setVerticalAlign (Ljava/lang/String;)V - public fun toString ()Ljava/lang/String; -} - -public final class com/posthog/internal/replay/RRUtilsKt { - public static final fun capture (Ljava/util/List;)V - public static final fun capture (Ljava/util/List;Lcom/posthog/PostHogInterface;)V - public static synthetic fun capture$default (Ljava/util/List;Lcom/posthog/PostHogInterface;ILjava/lang/Object;)V -} - -public final class com/posthog/internal/replay/RRWireframe { - public fun (IIIIILjava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;Ljava/lang/String;Lcom/posthog/internal/replay/RRStyle;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Integer;)V - public synthetic fun (IIIIILjava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;Ljava/lang/String;Lcom/posthog/internal/replay/RRStyle;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()I - public final fun component10 ()Ljava/lang/String; - public final fun component11 ()Ljava/lang/Object; - public final fun component12 ()Ljava/lang/String; - public final fun component13 ()Lcom/posthog/internal/replay/RRStyle; - public final fun component14 ()Ljava/lang/Boolean; - public final fun component15 ()Ljava/lang/Boolean; - public final fun component16 ()Ljava/util/List; - public final fun component17 ()Ljava/lang/Integer; - public final fun component18 ()Ljava/lang/Integer; - public final fun component2 ()I - public final fun component3 ()I - public final fun component4 ()I - public final fun component5 ()I - public final fun component6 ()Ljava/util/List; - public final fun component7 ()Ljava/lang/String; - public final fun component8 ()Ljava/lang/String; - public final fun component9 ()Ljava/lang/String; - public final fun copy (IIIIILjava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;Ljava/lang/String;Lcom/posthog/internal/replay/RRStyle;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Integer;)Lcom/posthog/internal/replay/RRWireframe; - public static synthetic fun copy$default (Lcom/posthog/internal/replay/RRWireframe;IIIIILjava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;Ljava/lang/String;Lcom/posthog/internal/replay/RRStyle;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Integer;ILjava/lang/Object;)Lcom/posthog/internal/replay/RRWireframe; - public fun equals (Ljava/lang/Object;)Z - public final fun getBase64 ()Ljava/lang/String; - public final fun getChecked ()Ljava/lang/Boolean; - public final fun getChildWireframes ()Ljava/util/List; - public final fun getDisabled ()Ljava/lang/Boolean; - public final fun getHeight ()I - public final fun getId ()I - public final fun getInputType ()Ljava/lang/String; - public final fun getLabel ()Ljava/lang/String; - public final fun getMax ()Ljava/lang/Integer; - public final fun getOptions ()Ljava/util/List; - public final fun getParentId ()Ljava/lang/Integer; - public final fun getStyle ()Lcom/posthog/internal/replay/RRStyle; - public final fun getText ()Ljava/lang/String; - public final fun getType ()Ljava/lang/String; - public final fun getValue ()Ljava/lang/Object; - public final fun getWidth ()I - public final fun getX ()I - public final fun getY ()I - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract interface class com/posthog/internal/surveys/PostHogSurveysHandler { - public abstract fun onEvent (Ljava/lang/String;Ljava/util/Map;)V - public static synthetic fun onEvent$default (Lcom/posthog/internal/surveys/PostHogSurveysHandler;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)V - public abstract fun onReset ()V - public abstract fun onSurveysLoaded (Ljava/util/List;)V -} - -public final class com/posthog/internal/surveys/PostHogSurveysHandler$DefaultImpls { - public static synthetic fun onEvent$default (Lcom/posthog/internal/surveys/PostHogSurveysHandler;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)V -} - -public final class com/posthog/internal/surveys/ResolvedSurveyTranslations { - public fun (Ljava/lang/String;Lcom/posthog/surveys/SurveyTranslation;Ljava/util/List;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lcom/posthog/surveys/SurveyTranslation; - public final fun component3 ()Ljava/util/List; - public final fun copy (Ljava/lang/String;Lcom/posthog/surveys/SurveyTranslation;Ljava/util/List;)Lcom/posthog/internal/surveys/ResolvedSurveyTranslations; - public static synthetic fun copy$default (Lcom/posthog/internal/surveys/ResolvedSurveyTranslations;Ljava/lang/String;Lcom/posthog/surveys/SurveyTranslation;Ljava/util/List;ILjava/lang/Object;)Lcom/posthog/internal/surveys/ResolvedSurveyTranslations; - public fun equals (Ljava/lang/Object;)Z - public final fun getMatchedKey ()Ljava/lang/String; - public final fun getQuestions ()Ljava/util/List; - public final fun getSurvey ()Lcom/posthog/surveys/SurveyTranslation; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/posthog/internal/surveys/SurveyLanguageDetectionKt { - public static final fun detectSurveyLanguage (Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;)Ljava/lang/String; -} - -public final class com/posthog/internal/surveys/SurveyTranslationApplierKt { - public static final fun resolveSurveyTranslations (Lcom/posthog/surveys/Survey;Ljava/lang/String;)Lcom/posthog/internal/surveys/ResolvedSurveyTranslations; -} - -public final class com/posthog/internal/surveys/SurveyUtilsKt { - public static final fun canActivateRepeatedly (Lcom/posthog/surveys/Survey;)Z - public static final fun hasEvents (Lcom/posthog/surveys/Survey;)Z - public static final fun hasWaitPeriodPassed (Lcom/posthog/surveys/Survey;Ljava/util/Date;Ljava/util/Date;)Z -} - public abstract interface class com/posthog/logs/PostHogBeforeSendLog { public abstract fun run (Lcom/posthog/logs/PostHogLogRecord;)Lcom/posthog/logs/PostHogLogRecord; } @@ -2418,9 +1524,3 @@ public final class com/posthog/surveys/SurveyType$Companion { public final fun fromValue (Ljava/lang/String;)Lcom/posthog/surveys/SurveyType; } -public final class com/posthog/vendor/uuid/TimeBasedEpochGenerator { - public static final field INSTANCE Lcom/posthog/vendor/uuid/TimeBasedEpochGenerator; - public final fun generate ()Ljava/util/UUID; - public final fun setDateProvider (Lcom/posthog/internal/PostHogDateProvider;)V -} - From 3a45b4508939a96dfa3a148ebddf582cfea0a980 Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Wed, 28 Oct 2026 19:46:51 -0400 Subject: [PATCH 2/9] feat: let wrapper SDKs build an event, run their own beforeSend, then send it --- .changeset/wrapper-build-send.md | 5 + posthog/src/main/java/com/posthog/PostHog.kt | 406 +++++++++++++----- .../main/java/com/posthog/PostHogInterface.kt | 51 +++ .../main/java/com/posthog/PostHogStateless.kt | 41 +- .../com/posthog/PostHogWrapperEventTest.kt | 193 +++++++++ 5 files changed, 577 insertions(+), 119 deletions(-) create mode 100644 .changeset/wrapper-build-send.md create mode 100644 posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt diff --git a/.changeset/wrapper-build-send.md b/.changeset/wrapper-build-send.md new file mode 100644 index 000000000..7a7fd89e5 --- /dev/null +++ b/.changeset/wrapper-build-send.md @@ -0,0 +1,5 @@ +--- +'posthog': patch +--- + +Add internal APIs so wrapper SDKs can run their own `beforeSend` on the fully built event before it's sent diff --git a/posthog/src/main/java/com/posthog/PostHog.kt b/posthog/src/main/java/com/posthog/PostHog.kt index ea0a756d9..a7fc47653 100644 --- a/posthog/src/main/java/com/posthog/PostHog.kt +++ b/posthog/src/main/java/com/posthog/PostHog.kt @@ -1104,132 +1104,249 @@ public class PostHog private constructor( // or replacing the event. var claimedReplayDebugBundle = false try { - if (!isEnabled()) { + val prepared = + prepareEvent( + event, + distinctId, + properties, + userProperties, + userPropertiesSetOnce, + groups, + timestamp, + options, + aiLane, + claimReplayDebugBundle = { claimReplayDebugPropertiesWindow().also { claimedReplayDebugBundle = it } }, + ) ?: return null + val postHogEvent = runBeforeSend(prepared.event, prepared.event.uuid) + if (postHogEvent == null) { + logDroppedEvent(event) return null } - if (isOptedOut()) { - config?.logger?.log("PostHog is in OptOut state.") - return null + return enqueueEvent(postHogEvent, event, prepared.mergedProperties, aiLane) { + if (claimedReplayDebugBundle) { + commitReplayDebugPropertiesWindow() + } } + } catch (e: Throwable) { + config?.logger?.log("Capture failed: $e.") + return null + } finally { + if (claimedReplayDebugBundle) { + releaseReplayDebugPropertiesClaim() + } + } + } - // A blank distinct ID falls back to the current one, like null, instead of PostHog dropping the event. - val newDistinctId = distinctId?.takeIf { it.isNotBlank() } ?: this.distinctId + /** + * An event enriched for capture, before the `beforeSend` chain runs. + * + * @property mergedProperties the enriched properties before the `$feature_flag_called` + * minimization, which the surveys and replay handlers read. + */ + private class PreparedEvent( + val event: PostHogEvent, + val mergedProperties: Map, + ) - // if the user isn't identified but passed userProperties, userPropertiesSetOnce or groups, - // we should still enable person processing since this is intentional - if (userProperties?.isEmpty() == false || userPropertiesSetOnce?.isEmpty() == false || groups?.isEmpty() == false) { - requirePersonProcessing("capture", ignoreMessage = true) - } + /** + * Checks and enriches one event without running `beforeSend` or queueing it. Returns null when + * the SDK wouldn't capture it. [claimReplayDebugBundle] is called only for events that may carry + * the replay debug bundle. + */ + private fun prepareEvent( + event: String, + distinctId: String?, + properties: Map?, + userProperties: Map?, + userPropertiesSetOnce: Map?, + groups: Map?, + timestamp: Date?, + options: Map?, + aiLane: Boolean, + claimReplayDebugBundle: () -> Boolean, + ): PreparedEvent? { + if (!isEnabled()) { + return null + } + if (isOptedOut()) { + config?.logger?.log("PostHog is in OptOut state.") + return null + } - // Automatically set person properties for feature flags during capture event - setPersonPropertiesForFlagsIfNeeded(userProperties, userPropertiesSetOnce) + // A blank distinct ID falls back to the current one, like null, instead of PostHog dropping the event. + val newDistinctId = distinctId?.takeIf { it.isNotBlank() } ?: this.distinctId - if (newDistinctId.isBlank()) { - config?.logger?.log("capture call not allowed, distinctId is invalid: $newDistinctId.") - return null - } + // if the user isn't identified but passed userProperties, userPropertiesSetOnce or groups, + // we should still enable person processing since this is intentional + if (userProperties?.isEmpty() == false || userPropertiesSetOnce?.isEmpty() == false || groups?.isEmpty() == false) { + requirePersonProcessing("capture", ignoreMessage = true) + } - // The AI lane always uses its own queue. - var isSnapshotEvent = !aiLane && event == PostHogEventName.SNAPSHOT.event - var groupIdentify = false - if (event == PostHogEventName.GROUP_IDENTIFY.event) { - groupIdentify = true - } + // Automatically set person properties for feature flags during capture event + setPersonPropertiesForFlagsIfNeeded(userProperties, userPropertiesSetOnce) - // Externally-built $exception events (e.g. the Flutter/RN bridge) carry only - // serialized class names, so they are matched by name instead of isInstance. - val ignored = config?.errorTrackingConfig?.ignoredExceptionTypes - if (!ignored.isNullOrEmpty() && - event == PostHogEventName.EXCEPTION.event && - hasIgnoredTypeInExceptionList(properties, ignored) - ) { - config?.logger?.log( - "Skipping \$exception: an entry in \$exception_list matches ignoredExceptionTypes", - ) - return null + if (newDistinctId.isBlank()) { + config?.logger?.log("capture call not allowed, distinctId is invalid: $newDistinctId.") + return null + } + + // The AI lane always uses its own queue. + val isSnapshotEvent = !aiLane && event == PostHogEventName.SNAPSHOT.event + var groupIdentify = false + if (event == PostHogEventName.GROUP_IDENTIFY.event) { + groupIdentify = true + } + + // Externally-built $exception events (e.g. the Flutter/RN bridge) carry only + // serialized class names, so they are matched by name instead of isInstance. + val ignored = config?.errorTrackingConfig?.ignoredExceptionTypes + if (!ignored.isNullOrEmpty() && + event == PostHogEventName.EXCEPTION.event && + hasIgnoredTypeInExceptionList(properties, ignored) + ) { + config?.logger?.log( + "Skipping \$exception: an entry in \$exception_list matches ignoredExceptionTypes", + ) + return null + } + + // Attach the buffered exception steps to any $exception event (unless the caller + // already supplied them), so externally-built exceptions (e.g. from the Flutter/RN + // bridge) carry steps too, not only those captured via captureException(throwable). + val stepsBuffer = exceptionStepsBuffer + val effectiveProperties = + if (event == PostHogEventName.EXCEPTION.event && stepsBuffer != null) { + val mutableProperties = properties?.toMutableMap() ?: mutableMapOf() + stepsBuffer.attachTo(mutableProperties) + mutableProperties + } else { + properties } - // Attach the buffered exception steps to any $exception event (unless the caller - // already supplied them), so externally-built exceptions (e.g. from the Flutter/RN - // bridge) carry steps too, not only those captured via captureException(throwable). - val stepsBuffer = exceptionStepsBuffer - val effectiveProperties = - if (event == PostHogEventName.EXCEPTION.event && stepsBuffer != null) { - val mutableProperties = properties?.toMutableMap() ?: mutableMapOf() - stepsBuffer.attachTo(mutableProperties) - mutableProperties - } else { - properties - } + val mergedProperties = + buildProperties( + newDistinctId, + properties = effectiveProperties, + userProperties = userProperties, + userPropertiesSetOnce = userPropertiesSetOnce, + groups = groups, + // only append shared props if not a snapshot event + appendSharedProps = !isSnapshotEvent, + // only append groups if not a group identify event and not a snapshot + appendGroups = !groupIdentify, + timestamp = timestamp, + claimReplayDebugBundle = { isReplayDebugEvent(event) && claimReplayDebugBundle() }, + ) - val mergedProperties = - buildProperties( - newDistinctId, - properties = effectiveProperties, - userProperties = userProperties, - userPropertiesSetOnce = userPropertiesSetOnce, - groups = groups, - // only append shared props if not a snapshot event - appendSharedProps = !isSnapshotEvent, - // only append groups if not a group identify event and not a snapshot - appendGroups = !groupIdentify, - timestamp = timestamp, - claimReplayDebugBundle = { - isReplayDebugEvent(event) && claimReplayDebugPropertiesWindow().also { claimedReplayDebugBundle = it } - }, - ) + val eventProperties = minimizeIfFeatureFlagCalled(event, mergedProperties) - val eventProperties = minimizeIfFeatureFlagCalled(event, mergedProperties) + // Snapshots don't carry the SDK's person-processing option. + val eventOptions = + if (isSnapshotEvent) options?.toMutableMap() else personProcessingOptions(options, mergedProperties) - // Snapshots don't carry the SDK's person-processing option. - val eventOptions = - if (isSnapshotEvent) options?.toMutableMap() else personProcessingOptions(options, mergedProperties) + // This capture's identity, kept through beforeSend. + val uuid = TimeBasedEpochGenerator.generate() + return PreparedEvent(newEvent(event, newDistinctId, eventProperties, timestamp, eventOptions, uuid), mergedProperties) + } - // This capture's identity, kept through beforeSend. - val uuid = TimeBasedEpochGenerator.generate() - val postHogEvent = buildEvent(event, newDistinctId, eventProperties, timestamp, eventOptions, uuid) - if (postHogEvent == null) { - val originalMessage = "PostHog event $event was dropped" - val message = - if (PostHogEventName.isUnsafeEditable(event)) { - "$originalMessage. This can cause unexpected behavior." - } else { - originalMessage - } - config?.logger?.log(message) - return null + private fun logDroppedEvent(event: String) { + val originalMessage = "PostHog event $event was dropped" + val message = + if (PostHogEventName.isUnsafeEditable(event)) { + "$originalMessage. This can cause unexpected behavior." + } else { + originalMessage } - // Reevaluate if this is a snapshot event because the event might have been updated by the beforeSend hook - isSnapshotEvent = !aiLane && postHogEvent.event == PostHogEventName.SNAPSHOT.event - // if this is a $snapshot event and $session_id is missing, don't process then event - if (isSnapshotEvent && postHogEvent.properties?.get("\$session_id") == null) { - config?.logger?.log("${postHogEvent.event} event dropped, because the \$session_id property is missing") + config?.logger?.log(message) + } + + /** + * Queues [postHogEvent], already through `beforeSend`, on its lane and notifies the surveys and + * replay handlers with [event] and [mergedProperties] from before the hook. Returns the event's + * uuid when it's queued. [onQueued] runs right after the event is added to the analytics or AI + * queue. + */ + private fun enqueueEvent( + postHogEvent: PostHogEvent, + event: String, + mergedProperties: Map, + aiLane: Boolean, + onQueued: () -> Unit = {}, + ): UUID? { + // Reevaluate if this is a snapshot event because the event might have been updated by the beforeSend hook + val isSnapshotEvent = !aiLane && postHogEvent.event == PostHogEventName.SNAPSHOT.event + // if this is a $snapshot event and $session_id is missing, don't process then event + if (isSnapshotEvent && postHogEvent.properties?.get("\$session_id") == null) { + config?.logger?.log("${postHogEvent.event} event dropped, because the \$session_id property is missing") + return null + } + // Replay has its own queue + if (isSnapshotEvent) { + replayQueue?.add(postHogEvent) + return postHogEvent.uuid + } + val targetQueue = if (aiLane) aiQueue ?: return null else queue + var queued = false + // beforeSend may have opted out + if (isOptedOut()) { + config?.logger?.log("PostHog is in OptOut state.") + } else if (aiLane && isOverAiEventLimit(postHogEvent)) { + // dropped here rather than in the queue, so captureAi returns null + } else if (targetQueue != null) { + targetQueue.add(postHogEvent) + queued = true + onQueued() + } + // Notify surveys integration about the event + surveysHandler?.onEvent(event, mergedProperties) + // Notify session replay handler about the event for event triggers + sessionReplayHandler?.onEvent(event, mergedProperties) + return if (queued) postHogEvent.uuid else null + } + + @PostHogInternal + override fun buildEventForWrapper( + event: String, + distinctId: String?, + properties: Map?, + userProperties: Map?, + userPropertiesSetOnce: Map?, + groups: Map?, + options: Map?, + ai: Boolean, + timestamp: Date?, + ): PostHogEvent? { + var claimedReplayDebugBundle = false + try { + if (!isEnabled()) { return null } - // Replay has its own queue - if (isSnapshotEvent) { - replayQueue?.add(postHogEvent) - return uuid - } - val targetQueue = if (aiLane) aiQueue ?: return null else queue - var queued = false - // beforeSend may have opted out - if (isOptedOut()) { - config?.logger?.log("PostHog is in OptOut state.") - } else if (aiLane && isOverAiEventLimit(postHogEvent)) { - // dropped here rather than in the queue, so captureAi returns null - } else if (targetQueue != null) { - targetQueue.add(postHogEvent) - queued = true - if (claimedReplayDebugBundle) { - commitReplayDebugPropertiesWindow() + val eventProperties = + if (!ai && event == PostHogEventName.SCREEN.event) { + val screenTitle = properties?.get("\$screen_name") as? String ?: return null + screenProperties(screenTitle, properties) ?: return null + } else { + properties } + val prepared = + prepareEvent( + event, + distinctId, + eventProperties, + userProperties, + userPropertiesSetOnce, + groups, + timestamp, + options, + ai, + claimReplayDebugBundle = { claimReplayDebugPropertiesWindow().also { claimedReplayDebugBundle = it } }, + ) ?: return null + // The bundle is in the built event, and the wrapper's send can't report back, so the + // window starts now. + if (claimedReplayDebugBundle) { + commitReplayDebugPropertiesWindow() } - // Notify surveys integration about the event - surveysHandler?.onEvent(event, mergedProperties) - // Notify session replay handler about the event for event triggers - sessionReplayHandler?.onEvent(event, mergedProperties) - return if (queued) uuid else null + return prepared.event } catch (e: Throwable) { config?.logger?.log("Capture failed: $e.") return null @@ -1240,6 +1357,35 @@ public class PostHog private constructor( } } + @PostHogInternal + override fun sendBuiltEventForWrapper( + event: PostHogEvent, + ai: Boolean, + ): UUID? { + try { + if (!isEnabled()) { + return null + } + if (isOptedOut()) { + config?.logger?.log("PostHog is in OptOut state.") + return null + } + val uuid = event.uuid ?: TimeBasedEpochGenerator.generate() + val builtEvent = if (event.uuid == null) event.copy(uuid = uuid) else event + // Copied before the hooks run, which can change the map in place. + val handlerProperties = builtEvent.properties?.toMap() ?: emptyMap() + val postHogEvent = runBeforeSend(builtEvent, uuid) + if (postHogEvent == null) { + logDroppedEvent(builtEvent.event) + return null + } + return enqueueEvent(postHogEvent, builtEvent.event, handlerProperties, ai) + } catch (e: Throwable) { + config?.logger?.log("Capture failed: $e.") + return null + } + } + override fun captureException( throwable: Throwable, properties: Map?, @@ -1602,9 +1748,22 @@ public class PostHog private constructor( return } + val props = screenProperties(screenTitle, properties) ?: return + + capture(PostHogEventName.SCREEN.event, properties = props, options = options) + } + + /** + * Caches [screenTitle], trimmed, as the current screen and returns [properties] with it as + * `$screen_name`. Returns null for a blank title. + */ + private fun screenProperties( + screenTitle: String, + properties: Map?, + ): MutableMap? { val trimmedTitle = screenTitle.trim() if (trimmedTitle.isEmpty()) { - return + return null } if (lastScreenName != trimmedTitle) config.notifyIntegrationsChanged() @@ -1620,8 +1779,7 @@ public class PostHog private constructor( // The title wins over a caller-supplied $screen_name, matching the cached value. props["\$screen_name"] = trimmedTitle - - capture(PostHogEventName.SCREEN.event, properties = props, options = options) + return props } public override fun alias( @@ -3078,6 +3236,36 @@ public class PostHog private constructor( return shared.getConfig() } + @PostHogInternal + override fun buildEventForWrapper( + event: String, + distinctId: String?, + properties: Map?, + userProperties: Map?, + userPropertiesSetOnce: Map?, + groups: Map?, + options: Map?, + ai: Boolean, + timestamp: Date?, + ): PostHogEvent? = + shared.buildEventForWrapper( + event, + distinctId = distinctId, + properties = properties, + userProperties = userProperties, + userPropertiesSetOnce = userPropertiesSetOnce, + groups = groups, + options = options, + ai = ai, + timestamp = timestamp, + ) + + @PostHogInternal + override fun sendBuiltEventForWrapper( + event: PostHogEvent, + ai: Boolean, + ): UUID? = shared.sendBuiltEventForWrapper(event, ai) + override fun isSessionActive(): Boolean { return shared.isSessionActive() } diff --git a/posthog/src/main/java/com/posthog/PostHogInterface.kt b/posthog/src/main/java/com/posthog/PostHogInterface.kt index 108335003..74edcde55 100644 --- a/posthog/src/main/java/com/posthog/PostHogInterface.kt +++ b/posthog/src/main/java/com/posthog/PostHogInterface.kt @@ -577,4 +577,55 @@ public interface PostHogInterface : PostHogCoreInterface { @PostHogInternal public fun getConfig(): T? + + /** + * For wrapper SDKs only (Flutter, React Native); apps use [capture] and [captureAi]. + * + * Builds the event that [capture] (or [captureAi] when [ai] is true) would send: the same + * checks, SDK and registered properties, per-call values, session, screen and options, with a + * new uuid and timestamp. It doesn't run `beforeSend` and doesn't queue the event, so a wrapper + * can run its own hook on the full event, then pass the result to [sendBuiltEventForWrapper]. + * A `$screen` event caches its `$screen_name` as the current screen, as [screen] does. + * + * Returns null when the SDK wouldn't capture the event: disabled, opted out, an ignored + * exception type, or a `$screen` event without a non-blank `$screen_name`. Defaults to null. + * + * ```kotlin + * val event = PostHog.buildEventForWrapper("purchase", properties = mapOf("plan" to "pro")) + * ?: return + * PostHog.sendBuiltEventForWrapper(wrapperBeforeSend(event) ?: return) + * ``` + * + * @suppress + */ + @PostHogInternal + public fun buildEventForWrapper( + event: String, + distinctId: String? = null, + properties: Map? = null, + userProperties: Map? = null, + userPropertiesSetOnce: Map? = null, + groups: Map? = null, + options: Map? = null, + ai: Boolean = false, + timestamp: Date? = null, + ): PostHogEvent? = null + + /** + * For wrapper SDKs only (Flutter, React Native); apps use [capture] and [captureAi]. + * + * Sends an event from [buildEventForWrapper] without adding properties again: runs the native + * `beforeSend` chain, then queues it on the analytics lane, or the AI lane when [ai] is true. + * The event keeps its uuid and timestamp. Legacy option properties are moved into options when + * the event is sent, as for [capture]. + * + * Returns the event's uuid once it's queued, or null when it's dropped. Defaults to null. + * + * @suppress + */ + @PostHogInternal + public fun sendBuiltEventForWrapper( + event: PostHogEvent, + ai: Boolean = false, + ): UUID? = null } diff --git a/posthog/src/main/java/com/posthog/PostHogStateless.kt b/posthog/src/main/java/com/posthog/PostHogStateless.kt index 104829ce5..eb5673141 100644 --- a/posthog/src/main/java/com/posthog/PostHogStateless.kt +++ b/posthog/src/main/java/com/posthog/PostHogStateless.kt @@ -308,16 +308,37 @@ public open class PostHogStateless protected constructor( timestamp: Date? = null, options: MutableMap? = null, uuid: UUID? = null, + ): PostHogEvent? = runBeforeSend(newEvent(event, distinctId, properties, timestamp, options, uuid), uuid) + + /** + * Creates the event, without running `beforeSend`. A null [timestamp] means now, and a null + * [uuid] gets a new UUID v7. + */ + internal fun newEvent( + event: String, + distinctId: String, + properties: MutableMap, + timestamp: Date? = null, + options: MutableMap? = null, + uuid: UUID? = null, + ): PostHogEvent = + PostHogEvent( + event, + distinctId, + properties = properties, + timestamp = timestamp ?: config?.dateProvider?.currentDate() ?: Date(), + uuid = uuid ?: TimeBasedEpochGenerator.generate(), + options = options, + ) + + /** + * Runs the `beforeSend` chain on [postHogEvent]. Returns null when a hook drops the event or + * throws. When [uuid] is given, the result keeps it. + */ + internal fun runBeforeSend( + postHogEvent: PostHogEvent, + uuid: UUID?, ): PostHogEvent? { - val postHogEvent = - PostHogEvent( - event, - distinctId, - properties = properties, - timestamp = timestamp ?: config?.dateProvider?.currentDate() ?: Date(), - uuid = uuid ?: TimeBasedEpochGenerator.generate(), - options = options, - ) var eventChecked: PostHogEvent = postHogEvent val beforeSendList = config?.beforeSendList ?: emptyList() @@ -326,7 +347,7 @@ public open class PostHogStateless protected constructor( try { val result = beforeSend.run(eventChecked) if (result == null) { - config?.logger?.log("Event $event was rejected in beforeSend function") + config?.logger?.log("Event ${postHogEvent.event} was rejected in beforeSend function") return null } eventChecked = result diff --git a/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt b/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt new file mode 100644 index 000000000..3de955c77 --- /dev/null +++ b/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt @@ -0,0 +1,193 @@ +package com.posthog + +import com.posthog.internal.PostHogMemoryPreferences +import com.posthog.internal.PostHogThreadFactory +import okhttp3.mockwebserver.MockWebServer +import okhttp3.mockwebserver.RecordedRequest +import org.junit.Rule +import org.junit.rules.TemporaryFolder +import java.io.File +import java.util.Date +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit +import kotlin.test.AfterTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +internal class PostHogWrapperEventTest { + @get:Rule + val tmpDir = TemporaryFolder() + + @get:Rule + val httpServers = TestHttpServers() + + private val queueExecutor = Executors.newSingleThreadScheduledExecutor(PostHogThreadFactory("TestQueue")) + private val replayQueueExecutor = Executors.newSingleThreadScheduledExecutor(PostHogThreadFactory("TestReplayQueue")) + private val logsExecutor = Executors.newSingleThreadScheduledExecutor(PostHogThreadFactory("TestLogsQueue")) + private val aiExecutor = Executors.newSingleThreadScheduledExecutor(PostHogThreadFactory("TestAiQueue")) + private val remoteConfigExecutor = Executors.newSingleThreadScheduledExecutor(PostHogThreadFactory("TestRemoteConfig")) + private val cachedEventsExecutor = Executors.newSingleThreadScheduledExecutor(PostHogThreadFactory("TestCachedEvents")) + private val clients = mutableListOf() + + private fun getSut( + http: MockWebServer, + configure: PostHogConfig.() -> Unit = {}, + ): PostHogInterface { + val storage = tmpDir.newFolder().absolutePath + val config = + PostHogConfig(PROJECT_TOKEN, http.url("/").toString()).apply { + storagePrefix = File(storage, "events").absolutePath + replayStoragePrefix = File(storage, "snapshots").absolutePath + logsStoragePrefix = File(storage, "logs").absolutePath + aiStoragePrefix = File(storage, "ai").absolutePath + flushAt = 1 + preloadFeatureFlags = false + sendFeatureFlagEvent = false + cachePreferences = PostHogMemoryPreferences() + context = TestPostHogContext() + configure() + } + return PostHog.withInternal( + config, + queueExecutor, + replayQueueExecutor, + remoteConfigExecutor, + cachedEventsExecutor, + reloadFeatureFlags = false, + logsExecutor = logsExecutor, + aiExecutor = aiExecutor, + ).also { clients.add(it) } + } + + @AfterTest + fun cleanup() { + clients.forEach { it.close() } + listOf(queueExecutor, replayQueueExecutor, logsExecutor, aiExecutor, remoteConfigExecutor, cachedEventsExecutor).forEach { + it.shutdownAndAwaitTermination() + } + tmpDir.root.deleteRecursively() + } + + private fun takeRequest(http: MockWebServer): RecordedRequest = assertNotNull(http.takeRequest(5, TimeUnit.SECONDS)) + + private fun events(request: RecordedRequest): List> = captureV1Events(request.body.unGzip()) + + @Suppress("UNCHECKED_CAST") + private fun Map.properties(): Map = this["properties"] as Map + + @Test + fun `build fills the event without running beforeSend or queueing it`() { + val http = httpServers.mockHttp() + var hookCalls = 0 + val sut = + getSut(http) { + addBeforeSend { event -> + hookCalls++ + event + } + } + sut.register("plan", "free") + + val event = + assertNotNull( + sut.buildEventForWrapper( + "purchase", + properties = mapOf("plan" to "pro"), + userProperties = mapOf("email" to "a@b.c"), + options = mapOf("cookieless_mode" to true), + ), + ) + + queueExecutor.awaitExecution() + assertEquals(0, http.requestCount) + assertEquals(0, hookCalls) + assertEquals("purchase", event.event) + assertEquals(sut.distinctId(), event.distinctId) + assertNotNull(event.uuid) + assertEquals("pro", event.properties?.get("plan")) + assertEquals("Android", event.properties?.get("\$os_name")) + assertEquals(mapOf("email" to "a@b.c"), event.properties?.get("\$set")) + assertEquals(true, event.options?.get("cookieless_mode")) + } + + @Test + fun `send keeps the built event's identity and properties, then runs beforeSend`() { + val http = httpServers.mockHttp() + val sut = + getSut(http) { + addBeforeSend { event -> + event.properties?.put("hooked", true) + event + } + } + val timestamp = Date(1_700_000_000_000) + val built = assertNotNull(sut.buildEventForWrapper("purchase", timestamp = timestamp)) + built.properties?.remove("\$os_name") + sut.register("late", "value") + + val uuid = sut.sendBuiltEventForWrapper(built) + + queueExecutor.awaitExecution() + val sent = events(takeRequest(http)).single() + assertEquals(built.uuid, uuid) + assertEquals(built.uuid.toString(), sent["uuid"]) + assertEquals("2023-11-14T22:13:20.000Z", sent["timestamp"]) + assertEquals(true, sent.properties()["hooked"]) + assertFalse(sent.properties().containsKey("\$os_name")) + assertFalse(sent.properties().containsKey("late")) + } + + @Test + fun `send on the AI lane posts to the AI endpoint`() { + val http = httpServers.mockHttp() + val sut = getSut(http) + val built = assertNotNull(sut.buildEventForWrapper("\$ai_generation", ai = true)) + + assertEquals(built.uuid, sut.sendBuiltEventForWrapper(built, ai = true)) + + aiExecutor.awaitExecution() + assertEquals("/i/v1/ai/events", takeRequest(http).path) + } + + @Test + fun `send returns null when beforeSend drops the event`() { + val http = httpServers.mockHttp() + val sut = getSut(http) { addBeforeSend { null } } + val built = assertNotNull(sut.buildEventForWrapper("purchase")) + + assertNull(sut.sendBuiltEventForWrapper(built)) + + queueExecutor.awaitExecution() + assertEquals(0, http.requestCount) + } + + @Test + fun `build returns null when opted out`() { + val sut = getSut(httpServers.mockHttp()) + sut.optOut() + + assertNull(sut.buildEventForWrapper("purchase")) + } + + @Test + fun `build of a screen event without a screen name returns null`() { + val sut = getSut(httpServers.mockHttp()) + + assertNull(sut.buildEventForWrapper("\$screen", properties = mapOf("\$screen_name" to " "))) + assertNull(sut.buildEventForWrapper("\$screen")) + } + + @Test + fun `build of a screen event caches its name for later events`() { + val sut = getSut(httpServers.mockHttp()) + + val screen = assertNotNull(sut.buildEventForWrapper("\$screen", properties = mapOf("\$screen_name" to " Home "))) + val next = assertNotNull(sut.buildEventForWrapper("tap")) + + assertEquals("Home", screen.properties?.get("\$screen_name")) + assertEquals("Home", next.properties?.get("\$screen_name")) + } +} From 5585bc141636eec0b7ea0cc18cbb76f2305316d9 Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Wed, 28 Oct 2026 19:55:32 -0400 Subject: [PATCH 3/9] fix: start the replay debug window when the wrapper's event is queued --- posthog/src/main/java/com/posthog/PostHog.kt | 25 +++++++++++++++---- .../main/java/com/posthog/PostHogInterface.kt | 3 ++- .../com/posthog/PostHogWrapperEventTest.kt | 13 ++++++++++ 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/posthog/src/main/java/com/posthog/PostHog.kt b/posthog/src/main/java/com/posthog/PostHog.kt index a7fc47653..8a4fb4729 100644 --- a/posthog/src/main/java/com/posthog/PostHog.kt +++ b/posthog/src/main/java/com/posthog/PostHog.kt @@ -129,6 +129,10 @@ public class PostHog private constructor( private var lastReplayDebugPropertiesAt: Long? = null private var replayDebugClaimOutstanding = false + // The wrapper-built event carrying the replay debug bundle, whose send starts the window. + // Guarded by replayDebugPropertiesLock. + private var wrapperReplayDebugEventUuid: UUID? = null + private var replayQueue: PostHogQueueInterface? = null private var logsQueue: PostHogQueueInterface? = null @@ -670,6 +674,7 @@ public class PostHog private constructor( synchronized(replayDebugPropertiesLock) { lastReplayDebugPropertiesAt = null replayDebugClaimOutstanding = false + wrapperReplayDebugEventUuid = null } PostHogSessionManager.setOnSessionIdChangedListener(null) @@ -1341,10 +1346,12 @@ public class PostHog private constructor( ai, claimReplayDebugBundle = { claimReplayDebugPropertiesWindow().also { claimedReplayDebugBundle = it } }, ) ?: return null - // The bundle is in the built event, and the wrapper's send can't report back, so the - // window starts now. + // The window starts when this event is queued, as for capture. The claim is released + // below, so another event may carry the bundle until then. if (claimedReplayDebugBundle) { - commitReplayDebugPropertiesWindow() + synchronized(replayDebugPropertiesLock) { + wrapperReplayDebugEventUuid = prepared.event.uuid + } } return prepared.event } catch (e: Throwable) { @@ -1373,13 +1380,21 @@ public class PostHog private constructor( val uuid = event.uuid ?: TimeBasedEpochGenerator.generate() val builtEvent = if (event.uuid == null) event.copy(uuid = uuid) else event // Copied before the hooks run, which can change the map in place. - val handlerProperties = builtEvent.properties?.toMap() ?: emptyMap() + val handlerProperties = + if (surveysHandler != null || sessionReplayHandler != null) builtEvent.properties?.toMap() ?: emptyMap() else emptyMap() val postHogEvent = runBeforeSend(builtEvent, uuid) if (postHogEvent == null) { logDroppedEvent(builtEvent.event) return null } - return enqueueEvent(postHogEvent, builtEvent.event, handlerProperties, ai) + return enqueueEvent(postHogEvent, builtEvent.event, handlerProperties, ai) { + synchronized(replayDebugPropertiesLock) { + if (wrapperReplayDebugEventUuid == uuid) { + wrapperReplayDebugEventUuid = null + lastReplayDebugPropertiesAt = currentTimeMillis() + } + } + } } catch (e: Throwable) { config?.logger?.log("Capture failed: $e.") return null diff --git a/posthog/src/main/java/com/posthog/PostHogInterface.kt b/posthog/src/main/java/com/posthog/PostHogInterface.kt index 74edcde55..a50cbd8b0 100644 --- a/posthog/src/main/java/com/posthog/PostHogInterface.kt +++ b/posthog/src/main/java/com/posthog/PostHogInterface.kt @@ -588,7 +588,7 @@ public interface PostHogInterface : PostHogCoreInterface { * A `$screen` event caches its `$screen_name` as the current screen, as [screen] does. * * Returns null when the SDK wouldn't capture the event: disabled, opted out, an ignored - * exception type, or a `$screen` event without a non-blank `$screen_name`. Defaults to null. + * exception type, or a `$screen` event (not on the AI lane) without a non-blank `$screen_name`. Defaults to null. * * ```kotlin * val event = PostHog.buildEventForWrapper("purchase", properties = mapOf("plan" to "pro")) @@ -618,6 +618,7 @@ public interface PostHogInterface : PostHogCoreInterface { * `beforeSend` chain, then queues it on the analytics lane, or the AI lane when [ai] is true. * The event keeps its uuid and timestamp. Legacy option properties are moved into options when * the event is sent, as for [capture]. + * Surveys and session replay event triggers see [event] as passed in, after the wrapper's hook. * * Returns the event's uuid once it's queued, or null when it's dropped. Defaults to null. * diff --git a/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt b/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt index 3de955c77..7a28ed463 100644 --- a/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt +++ b/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt @@ -164,6 +164,19 @@ internal class PostHogWrapperEventTest { assertEquals(0, http.requestCount) } + @Test + fun `send returns null after an opt-out since the build`() { + val http = httpServers.mockHttp() + val sut = getSut(http) + val built = assertNotNull(sut.buildEventForWrapper("purchase")) + sut.optOut() + + assertNull(sut.sendBuiltEventForWrapper(built)) + + queueExecutor.awaitExecution() + assertEquals(0, http.requestCount) + } + @Test fun `build returns null when opted out`() { val sut = getSut(httpServers.mockHttp()) From 31a71199f5ae52fd49a1eb9badc048402e3889d7 Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Wed, 28 Oct 2026 20:14:04 -0400 Subject: [PATCH 4/9] fix: defer wrapper build state changes and the replay debug claim to send A built event the wrapper drops no longer caches the screen name, updates the person properties used for flags, or claims the replay debug bundle. The send does all three, as capture does, so the window starts with the event that carries the bundle. --- posthog/src/main/java/com/posthog/PostHog.kt | 104 ++++++++++-------- .../main/java/com/posthog/PostHogInterface.kt | 6 +- .../com/posthog/PostHogWrapperEventTest.kt | 52 ++++++++- 3 files changed, 113 insertions(+), 49 deletions(-) diff --git a/posthog/src/main/java/com/posthog/PostHog.kt b/posthog/src/main/java/com/posthog/PostHog.kt index 8a4fb4729..0547dc551 100644 --- a/posthog/src/main/java/com/posthog/PostHog.kt +++ b/posthog/src/main/java/com/posthog/PostHog.kt @@ -129,10 +129,6 @@ public class PostHog private constructor( private var lastReplayDebugPropertiesAt: Long? = null private var replayDebugClaimOutstanding = false - // The wrapper-built event carrying the replay debug bundle, whose send starts the window. - // Guarded by replayDebugPropertiesLock. - private var wrapperReplayDebugEventUuid: UUID? = null - private var replayQueue: PostHogQueueInterface? = null private var logsQueue: PostHogQueueInterface? = null @@ -674,7 +670,6 @@ public class PostHog private constructor( synchronized(replayDebugPropertiesLock) { lastReplayDebugPropertiesAt = null replayDebugClaimOutstanding = false - wrapperReplayDebugEventUuid = null } PostHogSessionManager.setOnSessionIdChangedListener(null) @@ -1169,6 +1164,7 @@ public class PostHog private constructor( options: Map?, aiLane: Boolean, claimReplayDebugBundle: () -> Boolean, + setFlagPersonProperties: Boolean = true, ): PreparedEvent? { if (!isEnabled()) { return null @@ -1188,7 +1184,9 @@ public class PostHog private constructor( } // Automatically set person properties for feature flags during capture event - setPersonPropertiesForFlagsIfNeeded(userProperties, userPropertiesSetOnce) + if (setFlagPersonProperties) { + setPersonPropertiesForFlagsIfNeeded(userProperties, userPropertiesSetOnce) + } if (newDistinctId.isBlank()) { config?.logger?.log("capture call not allowed, distinctId is invalid: $newDistinctId.") @@ -1321,46 +1319,34 @@ public class PostHog private constructor( ai: Boolean, timestamp: Date?, ): PostHogEvent? { - var claimedReplayDebugBundle = false try { if (!isEnabled()) { return null } + // The wrapper may drop the event after this, so state changes wait for the send. val eventProperties = if (!ai && event == PostHogEventName.SCREEN.event) { val screenTitle = properties?.get("\$screen_name") as? String ?: return null - screenProperties(screenTitle, properties) ?: return null + screenProperties(screenTitle, properties, cacheScreenName = false) ?: return null } else { properties } - val prepared = - prepareEvent( - event, - distinctId, - eventProperties, - userProperties, - userPropertiesSetOnce, - groups, - timestamp, - options, - ai, - claimReplayDebugBundle = { claimReplayDebugPropertiesWindow().also { claimedReplayDebugBundle = it } }, - ) ?: return null - // The window starts when this event is queued, as for capture. The claim is released - // below, so another event may carry the bundle until then. - if (claimedReplayDebugBundle) { - synchronized(replayDebugPropertiesLock) { - wrapperReplayDebugEventUuid = prepared.event.uuid - } - } - return prepared.event + return prepareEvent( + event, + distinctId, + eventProperties, + userProperties, + userPropertiesSetOnce, + groups, + timestamp, + options, + ai, + claimReplayDebugBundle = { false }, + setFlagPersonProperties = false, + )?.event } catch (e: Throwable) { config?.logger?.log("Capture failed: $e.") return null - } finally { - if (claimedReplayDebugBundle) { - releaseReplayDebugPropertiesClaim() - } } } @@ -1369,6 +1355,7 @@ public class PostHog private constructor( event: PostHogEvent, ai: Boolean, ): UUID? { + var claimedReplayDebugBundle = false try { if (!isEnabled()) { return null @@ -1378,26 +1365,47 @@ public class PostHog private constructor( return null } val uuid = event.uuid ?: TimeBasedEpochGenerator.generate() - val builtEvent = if (event.uuid == null) event.copy(uuid = uuid) else event + val properties = event.properties ?: mutableMapOf() + @Suppress("UNCHECKED_CAST") + setPersonPropertiesForFlagsIfNeeded( + properties["\$set"] as? Map, + properties["\$set_once"] as? Map, + ) + if (!ai && event.event == PostHogEventName.SCREEN.event) { + (properties["\$screen_name"] as? String)?.trim()?.takeIf { it.isNotEmpty() }?.let { cacheScreenName(it) } + } + // The build added only the required replay debug keys. The full bundle is claimed here, + // so a built event the wrapper drops never holds or starts the window. + if (isReplayDebugEvent(event.event)) { + val debugProperties = + sdkDebugProperties(properties["\$session_id"] as? String, event.timestamp) { + claimReplayDebugPropertiesWindow().also { claimedReplayDebugBundle = it } + } + if (claimedReplayDebugBundle) { + debugProperties.forEach { (key, value) -> properties.putIfAbsent(key, value) } + } + } + val builtEvent = if (event.uuid == null || event.properties == null) event.copy(uuid = uuid, properties = properties) else event // Copied before the hooks run, which can change the map in place. val handlerProperties = - if (surveysHandler != null || sessionReplayHandler != null) builtEvent.properties?.toMap() ?: emptyMap() else emptyMap() + if (surveysHandler != null || sessionReplayHandler != null) properties.toMap() else emptyMap() val postHogEvent = runBeforeSend(builtEvent, uuid) if (postHogEvent == null) { logDroppedEvent(builtEvent.event) return null } return enqueueEvent(postHogEvent, builtEvent.event, handlerProperties, ai) { - synchronized(replayDebugPropertiesLock) { - if (wrapperReplayDebugEventUuid == uuid) { - wrapperReplayDebugEventUuid = null - lastReplayDebugPropertiesAt = currentTimeMillis() - } + if (claimedReplayDebugBundle) { + commitReplayDebugPropertiesWindow() } } } catch (e: Throwable) { config?.logger?.log("Capture failed: $e.") return null + } finally { + if (claimedReplayDebugBundle) { + releaseReplayDebugPropertiesClaim() + } } } @@ -1775,17 +1783,16 @@ public class PostHog private constructor( private fun screenProperties( screenTitle: String, properties: Map?, + cacheScreenName: Boolean = true, ): MutableMap? { val trimmedTitle = screenTitle.trim() if (trimmedTitle.isEmpty()) { return null } - if (lastScreenName != trimmedTitle) config.notifyIntegrationsChanged() - - // Cache for capture-time context snapshot on log records and for the - // $screen_name auto-attach on subsequent events (see buildProperties). - this.lastScreenName = trimmedTitle + if (cacheScreenName) { + cacheScreenName(trimmedTitle) + } val props = mutableMapOf() properties?.let { @@ -1797,6 +1804,13 @@ public class PostHog private constructor( return props } + // Cache for capture-time context snapshot on log records and for the + // $screen_name auto-attach on subsequent events (see buildProperties). + private fun cacheScreenName(screenName: String) { + if (lastScreenName != screenName) config.notifyIntegrationsChanged() + this.lastScreenName = screenName + } + public override fun alias( alias: String, options: Map?, diff --git a/posthog/src/main/java/com/posthog/PostHogInterface.kt b/posthog/src/main/java/com/posthog/PostHogInterface.kt index a50cbd8b0..4c4b27bb3 100644 --- a/posthog/src/main/java/com/posthog/PostHogInterface.kt +++ b/posthog/src/main/java/com/posthog/PostHogInterface.kt @@ -585,7 +585,8 @@ public interface PostHogInterface : PostHogCoreInterface { * checks, SDK and registered properties, per-call values, session, screen and options, with a * new uuid and timestamp. It doesn't run `beforeSend` and doesn't queue the event, so a wrapper * can run its own hook on the full event, then pass the result to [sendBuiltEventForWrapper]. - * A `$screen` event caches its `$screen_name` as the current screen, as [screen] does. + * It changes no SDK state, so a wrapper can drop the event: the current screen, the person + * properties used for flags and the replay debug window are updated by [sendBuiltEventForWrapper]. * * Returns null when the SDK wouldn't capture the event: disabled, opted out, an ignored * exception type, or a `$screen` event (not on the AI lane) without a non-blank `$screen_name`. Defaults to null. @@ -619,6 +620,9 @@ public interface PostHogInterface : PostHogCoreInterface { * The event keeps its uuid and timestamp. Legacy option properties are moved into options when * the event is sent, as for [capture]. * Surveys and session replay event triggers see [event] as passed in, after the wrapper's hook. + * Like [capture], it updates the person properties used for flags from `$set` and `$set_once`, + * caches a `$screen` event's `$screen_name` as the current screen, and may add the full replay + * debug bundle. * * Returns the event's uuid once it's queued, or null when it's dropped. Defaults to null. * diff --git a/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt b/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt index 7a28ed463..f99a1b946 100644 --- a/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt +++ b/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt @@ -1,6 +1,7 @@ package com.posthog import com.posthog.internal.PostHogMemoryPreferences +import com.posthog.internal.PostHogPreferences.Companion.PERSON_PROPERTIES_FOR_FLAGS import com.posthog.internal.PostHogThreadFactory import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.RecordedRequest @@ -194,13 +195,58 @@ internal class PostHogWrapperEventTest { } @Test - fun `build of a screen event caches its name for later events`() { - val sut = getSut(httpServers.mockHttp()) + fun `a built event the wrapper drops leaves no state behind`() { + val prefs = PostHogMemoryPreferences() + val sut = getSut(httpServers.mockHttp()) { cachePreferences = prefs } + + assertNotNull( + sut.buildEventForWrapper( + "\$screen", + properties = mapOf("\$screen_name" to "Home"), + userProperties = mapOf("plan" to "pro"), + ), + ) + val next = assertNotNull(sut.buildEventForWrapper("tap")) - val screen = assertNotNull(sut.buildEventForWrapper("\$screen", properties = mapOf("\$screen_name" to " Home "))) + assertFalse(next.properties!!.containsKey("\$screen_name")) + assertNull(prefs.getValue(PERSON_PROPERTIES_FOR_FLAGS)) + } + + @Test + fun `send caches the screen name and the person properties for flags`() { + val prefs = PostHogMemoryPreferences() + val sut = getSut(httpServers.mockHttp()) { cachePreferences = prefs } + val screen = + assertNotNull( + sut.buildEventForWrapper( + "\$screen", + properties = mapOf("\$screen_name" to " Home "), + userProperties = mapOf("plan" to "pro"), + ), + ) + + assertNotNull(sut.sendBuiltEventForWrapper(screen)) val next = assertNotNull(sut.buildEventForWrapper("tap")) assertEquals("Home", screen.properties?.get("\$screen_name")) assertEquals("Home", next.properties?.get("\$screen_name")) + assertEquals(mapOf("plan" to "pro"), prefs.getValue(PERSON_PROPERTIES_FOR_FLAGS)) + } + + @Test + fun `the replay debug bundle is claimed at send`() { + val http = httpServers.mockHttp() + val sut = getSut(http) { flushAt = 2 } + sut.startSession() + val first = assertNotNull(sut.buildEventForWrapper("\$first")) + val second = assertNotNull(sut.buildEventForWrapper("\$second")) + + assertNotNull(sut.sendBuiltEventForWrapper(first)) + assertNotNull(sut.sendBuiltEventForWrapper(second)) + + queueExecutor.awaitExecution() + val (sentFirst, sentSecond) = events(takeRequest(http)) + assertNotNull(sentFirst.properties()["\$sdk_debug_session_start"]) + assertFalse(sentSecond.properties().containsKey("\$sdk_debug_session_start")) } } From e73e33b84e4307a36feede89a2e8702bf89f68db Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Wed, 28 Oct 2026 21:04:53 -0400 Subject: [PATCH 5/9] fix: keep wrapper builds state-free and send each built event once Build defers person processing to send and records the built name for replay debug eligibility. Send adds only the throttled replay debug keys (API 23 safe, keeps keys the wrapper's hook removed), skips flag person properties when the user changed since the build, and ignores a second send of the same event. --- posthog/src/main/java/com/posthog/PostHog.kt | 136 ++++++++++++++---- .../main/java/com/posthog/PostHogInterface.kt | 13 +- .../com/posthog/PostHogWrapperEventTest.kt | 72 ++++++++++ 3 files changed, 192 insertions(+), 29 deletions(-) diff --git a/posthog/src/main/java/com/posthog/PostHog.kt b/posthog/src/main/java/com/posthog/PostHog.kt index 0547dc551..8c6007d0c 100644 --- a/posthog/src/main/java/com/posthog/PostHog.kt +++ b/posthog/src/main/java/com/posthog/PostHog.kt @@ -65,6 +65,19 @@ private val REQUIRED_REPLAY_DEBUG_PROPERTY_KEYS = "\$sdk_debug_replay_internal_buffer_length", ) +// On every event whether or not the bundle is claimed; the rest of the bundle needs the claim. +private val UNTHROTTLED_DEBUG_PROPERTY_KEYS = + REQUIRED_REPLAY_DEBUG_PROPERTY_KEYS + setOf("\$sdk_debug_pending_queue_size", "\$sdk_debug_error_capturing_properties") + +// Wrapper builds waiting for their send, and recent sends. A build the wrapper drops is evicted by newer ones. +private const val MAX_TRACKED_WRAPPER_EVENTS = 1000 + +private fun requiresPersonProcessing( + userProperties: Map?, + userPropertiesSetOnce: Map?, + groups: Map?, +): Boolean = userProperties?.isEmpty() == false || userPropertiesSetOnce?.isEmpty() == false || groups?.isEmpty() == false + private fun isReplayDebugEvent(event: String): Boolean = event.startsWith("\$") && event != PostHogEventName.FEATURE_FLAG_CALLED.event && @@ -129,6 +142,10 @@ public class PostHog private constructor( private var lastReplayDebugPropertiesAt: Long? = null private var replayDebugClaimOutstanding = false + // Keyed by event uuid in build order. Both guarded by pendingWrapperBuilds. + private val pendingWrapperBuilds = LinkedHashMap() + private val sentWrapperEvents = LinkedHashSet() + private var replayQueue: PostHogQueueInterface? = null private var logsQueue: PostHogQueueInterface? = null @@ -976,15 +993,20 @@ public class PostHog private constructor( * Adds `process_person_profile: false` when the SDK doesn't process person profiles for this * event, unless the caller set that option or [properties] carry a non-null legacy * `$process_person_profile` property (per call or registered). Nothing is added otherwise: - * PostHog processes person profiles by default. Returns a copy, so `beforeSend` edits never - * reach the caller's map. + * PostHog processes person profiles by default. [personProcessingPending] counts a person + * processing change a wrapper build defers to its send. Returns a copy, so `beforeSend` edits + * never reach the caller's map. */ private fun personProcessingOptions( options: Map?, properties: Map, + personProcessingPending: Boolean = false, ): MutableMap? { val eventOptions = options?.toMutableMap() - if (hasPersonProcessing() || properties["\$process_person_profile"] != null) { + if ((personProcessingPending && config?.personProfiles != PersonProfiles.NEVER) || + hasPersonProcessing() || + properties["\$process_person_profile"] != null + ) { return eventOptions } val result = eventOptions ?: mutableMapOf() @@ -1148,6 +1170,16 @@ public class PostHog private constructor( val mergedProperties: Map, ) + /** + * What [buildEventForWrapper] decided from the call, read back by [sendBuiltEventForWrapper]: + * the wrapper's hook can rename the event, and a build changes no SDK state. + */ + private class WrapperBuild( + val replayDebugEligible: Boolean, + val requiresPersonProcessing: Boolean, + val sdkDistinctId: String, + ) + /** * Checks and enriches one event without running `beforeSend` or queueing it. Returns null when * the SDK wouldn't capture it. [claimReplayDebugBundle] is called only for events that may carry @@ -1165,6 +1197,7 @@ public class PostHog private constructor( aiLane: Boolean, claimReplayDebugBundle: () -> Boolean, setFlagPersonProperties: Boolean = true, + deferPersonProcessing: Boolean = false, ): PreparedEvent? { if (!isEnabled()) { return null @@ -1179,7 +1212,8 @@ public class PostHog private constructor( // if the user isn't identified but passed userProperties, userPropertiesSetOnce or groups, // we should still enable person processing since this is intentional - if (userProperties?.isEmpty() == false || userPropertiesSetOnce?.isEmpty() == false || groups?.isEmpty() == false) { + val requiresPersonProcessing = requiresPersonProcessing(userProperties, userPropertiesSetOnce, groups) + if (requiresPersonProcessing && !deferPersonProcessing) { requirePersonProcessing("capture", ignoreMessage = true) } @@ -1245,7 +1279,11 @@ public class PostHog private constructor( // Snapshots don't carry the SDK's person-processing option. val eventOptions = - if (isSnapshotEvent) options?.toMutableMap() else personProcessingOptions(options, mergedProperties) + if (isSnapshotEvent) { + options?.toMutableMap() + } else { + personProcessingOptions(options, mergedProperties, deferPersonProcessing && requiresPersonProcessing) + } // This capture's identity, kept through beforeSend. val uuid = TimeBasedEpochGenerator.generate() @@ -1331,19 +1369,36 @@ public class PostHog private constructor( } else { properties } - return prepareEvent( - event, - distinctId, - eventProperties, - userProperties, - userPropertiesSetOnce, - groups, - timestamp, - options, - ai, - claimReplayDebugBundle = { false }, - setFlagPersonProperties = false, - )?.event + val built = + prepareEvent( + event, + distinctId, + eventProperties, + userProperties, + userPropertiesSetOnce, + groups, + timestamp, + options, + ai, + claimReplayDebugBundle = { false }, + setFlagPersonProperties = false, + deferPersonProcessing = true, + )?.event ?: return null + built.uuid?.let { uuid -> + val build = + WrapperBuild( + replayDebugEligible = isReplayDebugEvent(event), + requiresPersonProcessing = requiresPersonProcessing(userProperties, userPropertiesSetOnce, groups), + sdkDistinctId = this.distinctId, + ) + synchronized(pendingWrapperBuilds) { + pendingWrapperBuilds[uuid] = build + if (pendingWrapperBuilds.size > MAX_TRACKED_WRAPPER_EVENTS) { + pendingWrapperBuilds.remove(pendingWrapperBuilds.keys.first()) + } + } + } + return built } catch (e: Throwable) { config?.logger?.log("Capture failed: $e.") return null @@ -1357,6 +1412,22 @@ public class PostHog private constructor( ): UUID? { var claimedReplayDebugBundle = false try { + var alreadySent = false + val build = + event.uuid?.let { uuid -> + synchronized(pendingWrapperBuilds) { + // Checked and recorded together, so two concurrent sends can't both queue it. + alreadySent = !sentWrapperEvents.add(uuid) + if (sentWrapperEvents.size > MAX_TRACKED_WRAPPER_EVENTS) { + sentWrapperEvents.remove(sentWrapperEvents.first()) + } + pendingWrapperBuilds.remove(uuid) + } + } + if (alreadySent) { + config?.logger?.log("Event ${event.uuid} was already sent.") + return null + } if (!isEnabled()) { return null } @@ -1364,25 +1435,38 @@ public class PostHog private constructor( config?.logger?.log("PostHog is in OptOut state.") return null } + if (build?.requiresPersonProcessing == true) { + requirePersonProcessing("capture", ignoreMessage = true) + } val uuid = event.uuid ?: TimeBasedEpochGenerator.generate() val properties = event.properties ?: mutableMapOf() - @Suppress("UNCHECKED_CAST") - setPersonPropertiesForFlagsIfNeeded( - properties["\$set"] as? Map, - properties["\$set_once"] as? Map, - ) + // Skipped when reset() or identify() changed the user since the build: the event belongs + // to the previous one. + if (build == null || build.sdkDistinctId == this.distinctId) { + @Suppress("UNCHECKED_CAST") + setPersonPropertiesForFlagsIfNeeded( + properties["\$set"] as? Map, + properties["\$set_once"] as? Map, + ) + } if (!ai && event.event == PostHogEventName.SCREEN.event) { (properties["\$screen_name"] as? String)?.trim()?.takeIf { it.isNotEmpty() }?.let { cacheScreenName(it) } } // The build added only the required replay debug keys. The full bundle is claimed here, - // so a built event the wrapper drops never holds or starts the window. - if (isReplayDebugEvent(event.event)) { + // so a built event the wrapper drops never holds or starts the window. Eligibility comes + // from the name the event was built with, since the wrapper's hook can rename it. + if (build?.replayDebugEligible ?: isReplayDebugEvent(event.event)) { val debugProperties = sdkDebugProperties(properties["\$session_id"] as? String, event.timestamp) { claimReplayDebugPropertiesWindow().also { claimedReplayDebugBundle = it } } if (claimedReplayDebugBundle) { - debugProperties.forEach { (key, value) -> properties.putIfAbsent(key, value) } + // Only the keys the build left out, so a key the wrapper's hook removed stays removed. + debugProperties.forEach { (key, value) -> + if (key !in UNTHROTTLED_DEBUG_PROPERTY_KEYS && !properties.containsKey(key)) { + properties[key] = value + } + } } } val builtEvent = if (event.uuid == null || event.properties == null) event.copy(uuid = uuid, properties = properties) else event diff --git a/posthog/src/main/java/com/posthog/PostHogInterface.kt b/posthog/src/main/java/com/posthog/PostHogInterface.kt index 4c4b27bb3..2160a9c89 100644 --- a/posthog/src/main/java/com/posthog/PostHogInterface.kt +++ b/posthog/src/main/java/com/posthog/PostHogInterface.kt @@ -586,7 +586,10 @@ public interface PostHogInterface : PostHogCoreInterface { * new uuid and timestamp. It doesn't run `beforeSend` and doesn't queue the event, so a wrapper * can run its own hook on the full event, then pass the result to [sendBuiltEventForWrapper]. * It changes no SDK state, so a wrapper can drop the event: the current screen, the person - * properties used for flags and the replay debug window are updated by [sendBuiltEventForWrapper]. + * properties used for flags, person processing turned on by [userProperties], + * [userPropertiesSetOnce] or [groups], and the replay debug window are updated by + * [sendBuiltEventForWrapper]. That send uses the event name passed here to decide whether the + * replay debug bundle applies, even if the wrapper's hook renamed the event. * * Returns null when the SDK wouldn't capture the event: disabled, opted out, an ignored * exception type, or a `$screen` event (not on the AI lane) without a non-blank `$screen_name`. Defaults to null. @@ -621,8 +624,12 @@ public interface PostHogInterface : PostHogCoreInterface { * the event is sent, as for [capture]. * Surveys and session replay event triggers see [event] as passed in, after the wrapper's hook. * Like [capture], it updates the person properties used for flags from `$set` and `$set_once`, - * caches a `$screen` event's `$screen_name` as the current screen, and may add the full replay - * debug bundle. + * caches a `$screen` event's `$screen_name` as the current screen, turns person processing on + * when the build had user properties or groups, and may add the replay debug bundle keys the + * event lacks. Keys the wrapper's hook removed aren't added back. + * + * The person properties for flags are skipped when `reset()` or `identify()` changed the user + * since the build. A second send of the same built event does nothing and returns null. * * Returns the event's uuid once it's queued, or null when it's dropped. Defaults to null. * diff --git a/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt b/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt index f99a1b946..98c50ff90 100644 --- a/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt +++ b/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt @@ -210,6 +210,19 @@ internal class PostHogWrapperEventTest { assertFalse(next.properties!!.containsKey("\$screen_name")) assertNull(prefs.getValue(PERSON_PROPERTIES_FOR_FLAGS)) + assertEquals(false, next.options?.get("process_person_profile")) + } + + @Test + fun `send turns on person processing for a build with user properties`() { + val sut = getSut(httpServers.mockHttp()) + val identified = assertNotNull(sut.buildEventForWrapper("signup", userProperties = mapOf("plan" to "pro"))) + + assertNotNull(sut.sendBuiltEventForWrapper(identified)) + val next = assertNotNull(sut.buildEventForWrapper("tap")) + + assertNull(identified.options?.get("process_person_profile")) + assertNull(next.options?.get("process_person_profile")) } @Test @@ -249,4 +262,63 @@ internal class PostHogWrapperEventTest { assertNotNull(sentFirst.properties()["\$sdk_debug_session_start"]) assertFalse(sentSecond.properties().containsKey("\$sdk_debug_session_start")) } + + @Test + fun `replay debug eligibility follows the built name across a rename`() { + val http = httpServers.mockHttp() + val sut = getSut(http) { flushAt = 2 } + sut.startSession() + val renamedAway = assertNotNull(sut.buildEventForWrapper("\$first")) + val renamedTo = assertNotNull(sut.buildEventForWrapper("tap")) + + assertNotNull(sut.sendBuiltEventForWrapper(renamedTo.copy(event = "\$second"))) + assertNotNull(sut.sendBuiltEventForWrapper(renamedAway.copy(event = "custom"))) + + queueExecutor.awaitExecution() + val (sentTo, sentAway) = events(takeRequest(http)) + assertNotNull(sentAway.properties()["\$sdk_debug_session_start"]) + assertFalse(sentTo.properties().containsKey("\$sdk_debug_session_start")) + } + + @Test + fun `send keeps a replay debug key the wrapper removed`() { + val http = httpServers.mockHttp() + val sut = getSut(http) + sut.startSession() + val event = assertNotNull(sut.buildEventForWrapper("\$first")) + event.properties?.remove("\$recording_status") + + assertNotNull(sut.sendBuiltEventForWrapper(event)) + + queueExecutor.awaitExecution() + val sent = events(takeRequest(http)).single().properties() + assertNotNull(sent["\$sdk_debug_session_start"]) + assertFalse(sent.containsKey("\$recording_status")) + } + + @Test + fun `a second send of the same built event is a no-op`() { + val http = httpServers.mockHttp() + val sut = getSut(http) { flushAt = 2 } + val event = assertNotNull(sut.buildEventForWrapper("tap")) + + assertNotNull(sut.sendBuiltEventForWrapper(event)) + assertNull(sut.sendBuiltEventForWrapper(event)) + assertNotNull(sut.sendBuiltEventForWrapper(assertNotNull(sut.buildEventForWrapper("next")))) + + queueExecutor.awaitExecution() + assertEquals(listOf("tap", "next"), events(takeRequest(http)).map { it["event"] }) + } + + @Test + fun `send skips the person properties for flags after the user changed`() { + val prefs = PostHogMemoryPreferences() + val sut = getSut(httpServers.mockHttp()) { cachePreferences = prefs } + val event = assertNotNull(sut.buildEventForWrapper("signup", userProperties = mapOf("plan" to "pro"))) + + sut.reset() + assertNotNull(sut.sendBuiltEventForWrapper(event)) + + assertNull(prefs.getValue(PERSON_PROPERTIES_FOR_FLAGS)) + } } From 887cae5a209c1727933b5a25f3fde4b3447651e9 Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Wed, 28 Oct 2026 21:26:25 -0400 Subject: [PATCH 6/9] fix: tie a wrapper send to the build's setup, instance and identity A send now drops an event this instance didn't build or built before setup()/close(), skips person processing and flag person properties after reset() (also when reuseAnonymousId keeps the distinct id), and adds the replay debug values snapshotted at build. --- posthog/src/main/java/com/posthog/PostHog.kt | 77 +++++++++++++------ .../main/java/com/posthog/PostHogInterface.kt | 9 ++- .../com/posthog/PostHogWrapperEventTest.kt | 65 ++++++++++++++++ 3 files changed, 124 insertions(+), 27 deletions(-) diff --git a/posthog/src/main/java/com/posthog/PostHog.kt b/posthog/src/main/java/com/posthog/PostHog.kt index 8c6007d0c..c948ecfc0 100644 --- a/posthog/src/main/java/com/posthog/PostHog.kt +++ b/posthog/src/main/java/com/posthog/PostHog.kt @@ -69,7 +69,8 @@ private val REQUIRED_REPLAY_DEBUG_PROPERTY_KEYS = private val UNTHROTTLED_DEBUG_PROPERTY_KEYS = REQUIRED_REPLAY_DEBUG_PROPERTY_KEYS + setOf("\$sdk_debug_pending_queue_size", "\$sdk_debug_error_capturing_properties") -// Wrapper builds waiting for their send, and recent sends. A build the wrapper drops is evicted by newer ones. +// Wrapper builds waiting for their send, and recent sends. A build the wrapper drops is evicted by +// newer ones; an evicted build can no longer be sent. private const val MAX_TRACKED_WRAPPER_EVENTS = 1000 private fun requiresPersonProcessing( @@ -146,6 +147,11 @@ public class PostHog private constructor( private val pendingWrapperBuilds = LinkedHashMap() private val sentWrapperEvents = LinkedHashSet() + // Incremented by reset(), so a send can tell the user changed since the build even when + // reuseAnonymousId keeps the distinct id the same. + @Volatile + private var resetGeneration = 0L + private var replayQueue: PostHogQueueInterface? = null private var logsQueue: PostHogQueueInterface? = null @@ -405,6 +411,7 @@ public class PostHog private constructor( applyBootstrapIfNeeded(config) + clearWrapperBuilds() super.enabled = true // Initialize device_id if not already set. getDeviceId() handles lazy init @@ -684,6 +691,7 @@ public class PostHog private constructor( featureFlagsCalled.clear() lastScreenName = null + clearWrapperBuilds() synchronized(replayDebugPropertiesLock) { lastReplayDebugPropertiesAt = null replayDebugClaimOutstanding = false @@ -1173,11 +1181,14 @@ public class PostHog private constructor( /** * What [buildEventForWrapper] decided from the call, read back by [sendBuiltEventForWrapper]: * the wrapper's hook can rename the event, and a build changes no SDK state. + * [deferredReplayDebug] holds the replay debug values from the build, sent only if the send + * claims the window; null when the event can't carry them. */ private class WrapperBuild( - val replayDebugEligible: Boolean, + val deferredReplayDebug: Map?, val requiresPersonProcessing: Boolean, val sdkDistinctId: String, + val resetGeneration: Long, ) /** @@ -1385,11 +1396,21 @@ public class PostHog private constructor( deferPersonProcessing = true, )?.event ?: return null built.uuid?.let { uuid -> + // Snapshotted now, so a new session during the round trip doesn't change them. + val deferredReplayDebug = + if (!ai && isReplayDebugEvent(event)) { + sdkDebugProperties(built.properties?.get("\$session_id") as? String, built.timestamp) { true } + .filterKeys { it !in UNTHROTTLED_DEBUG_PROPERTY_KEYS } + .takeIf { it.isNotEmpty() } + } else { + null + } val build = WrapperBuild( - replayDebugEligible = isReplayDebugEvent(event), + deferredReplayDebug = deferredReplayDebug, requiresPersonProcessing = requiresPersonProcessing(userProperties, userPropertiesSetOnce, groups), sdkDistinctId = this.distinctId, + resetGeneration = resetGeneration, ) synchronized(pendingWrapperBuilds) { pendingWrapperBuilds[uuid] = build @@ -1405,6 +1426,12 @@ public class PostHog private constructor( } } + private fun clearWrapperBuilds() { + synchronized(pendingWrapperBuilds) { + pendingWrapperBuilds.clear() + } + } + @PostHogInternal override fun sendBuiltEventForWrapper( event: PostHogEvent, @@ -1428,6 +1455,12 @@ public class PostHog private constructor( config?.logger?.log("Event ${event.uuid} was already sent.") return null } + // Builds are cleared on setup() and close() and kept per instance, so this also drops an + // event built by another instance or before a reconfigure. + if (build == null) { + config?.logger?.log("Event ${event.event} wasn't built by buildEventForWrapper on this setup, so it was dropped.") + return null + } if (!isEnabled()) { return null } @@ -1435,14 +1468,15 @@ public class PostHog private constructor( config?.logger?.log("PostHog is in OptOut state.") return null } - if (build?.requiresPersonProcessing == true) { + // Person processing and flag person properties apply only while the SDK still has the + // identity the event was built for: after a reset() or identify(), it belongs to the previous one. + val sameIdentity = build.sdkDistinctId == this.distinctId && build.resetGeneration == resetGeneration + if (sameIdentity && build.requiresPersonProcessing) { requirePersonProcessing("capture", ignoreMessage = true) } - val uuid = event.uuid ?: TimeBasedEpochGenerator.generate() + val uuid = event.uuid val properties = event.properties ?: mutableMapOf() - // Skipped when reset() or identify() changed the user since the build: the event belongs - // to the previous one. - if (build == null || build.sdkDistinctId == this.distinctId) { + if (sameIdentity) { @Suppress("UNCHECKED_CAST") setPersonPropertiesForFlagsIfNeeded( properties["\$set"] as? Map, @@ -1452,24 +1486,20 @@ public class PostHog private constructor( if (!ai && event.event == PostHogEventName.SCREEN.event) { (properties["\$screen_name"] as? String)?.trim()?.takeIf { it.isNotEmpty() }?.let { cacheScreenName(it) } } - // The build added only the required replay debug keys. The full bundle is claimed here, - // so a built event the wrapper drops never holds or starts the window. Eligibility comes - // from the name the event was built with, since the wrapper's hook can rename it. - if (build?.replayDebugEligible ?: isReplayDebugEvent(event.event)) { - val debugProperties = - sdkDebugProperties(properties["\$session_id"] as? String, event.timestamp) { - claimReplayDebugPropertiesWindow().also { claimedReplayDebugBundle = it } - } - if (claimedReplayDebugBundle) { - // Only the keys the build left out, so a key the wrapper's hook removed stays removed. - debugProperties.forEach { (key, value) -> - if (key !in UNTHROTTLED_DEBUG_PROPERTY_KEYS && !properties.containsKey(key)) { - properties[key] = value - } + // The build added only the required replay debug keys. The window is claimed here, so a + // built event the wrapper drops never holds or starts it. Eligibility and values come from + // the build, since the wrapper's hook can rename the event and the session can change. + val deferredReplayDebug = build.deferredReplayDebug + if (!ai && deferredReplayDebug != null && claimReplayDebugPropertiesWindow()) { + claimedReplayDebugBundle = true + // Only keys the event lacks, so the wrapper's values win. + deferredReplayDebug.forEach { (key, value) -> + if (!properties.containsKey(key)) { + properties[key] = value } } } - val builtEvent = if (event.uuid == null || event.properties == null) event.copy(uuid = uuid, properties = properties) else event + val builtEvent = if (event.properties == null) event.copy(properties = properties) else event // Copied before the hooks run, which can change the map in place. val handlerProperties = if (surveysHandler != null || sessionReplayHandler != null) properties.toMap() else emptyMap() @@ -2639,6 +2669,7 @@ public class PostHog private constructor( } config.notifyIntegrationsChanged() + resetGeneration++ // Capture the logging-out identity before preferences are cleared, so the push token can be // unregistered for it and re-registered under the new anonymous id (decision 5/6). val previousDistinctId = distinctId diff --git a/posthog/src/main/java/com/posthog/PostHogInterface.kt b/posthog/src/main/java/com/posthog/PostHogInterface.kt index 2160a9c89..9d116baca 100644 --- a/posthog/src/main/java/com/posthog/PostHogInterface.kt +++ b/posthog/src/main/java/com/posthog/PostHogInterface.kt @@ -625,11 +625,12 @@ public interface PostHogInterface : PostHogCoreInterface { * Surveys and session replay event triggers see [event] as passed in, after the wrapper's hook. * Like [capture], it updates the person properties used for flags from `$set` and `$set_once`, * caches a `$screen` event's `$screen_name` as the current screen, turns person processing on - * when the build had user properties or groups, and may add the replay debug bundle keys the - * event lacks. Keys the wrapper's hook removed aren't added back. + * when the build had user properties or groups, and may add the replay debug values snapshotted + * at build that the event lacks. Keys the wrapper's hook removed aren't added back. * - * The person properties for flags are skipped when `reset()` or `identify()` changed the user - * since the build. A second send of the same built event does nothing and returns null. + * Person processing and the person properties for flags are skipped when `reset()` or + * `identify()` changed the user since the build. The event is dropped when this instance didn't + * build it, when `setup()` or `close()` ran since the build, or when it was already sent. * * Returns the event's uuid once it's queued, or null when it's dropped. Defaults to null. * diff --git a/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt b/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt index 98c50ff90..87396f6cb 100644 --- a/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt +++ b/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt @@ -321,4 +321,69 @@ internal class PostHogWrapperEventTest { assertNull(prefs.getValue(PERSON_PROPERTIES_FOR_FLAGS)) } + + @Test + fun `send skips the person properties for flags after a reset that kept the anonymous id`() { + val prefs = PostHogMemoryPreferences() + val sut = + getSut(httpServers.mockHttp()) { + cachePreferences = prefs + reuseAnonymousId = true + } + val event = assertNotNull(sut.buildEventForWrapper("signup", userProperties = mapOf("plan" to "pro"))) + + sut.reset() + assertNotNull(sut.sendBuiltEventForWrapper(event)) + + assertNull(prefs.getValue(PERSON_PROPERTIES_FOR_FLAGS)) + } + + @Test + fun `send drops an event built by another instance`() { + val built = assertNotNull(getSut(httpServers.mockHttp()).buildEventForWrapper("tap")) + val other = getSut(httpServers.mockHttp()) + + assertNull(other.sendBuiltEventForWrapper(built)) + } + + @Test + fun `send drops an event built before the SDK was set up again`() { + val http = httpServers.mockHttp() + var setupConfig: PostHogConfig? = null + val sut = getSut(http) { setupConfig = this } + val built = assertNotNull(sut.buildEventForWrapper("tap")) + + sut.close() + sut.setup(assertNotNull(setupConfig)) + + assertNull(sut.sendBuiltEventForWrapper(built)) + } + + @Test + fun `send keeps the replay debug values from the build`() { + val http = httpServers.mockHttp() + val sut = getSut(http) + sut.startSession() + val built = assertNotNull(sut.buildEventForWrapper("\$first")) + sut.endSession() + Thread.sleep(5) + sut.startSession() + + assertNotNull(sut.sendBuiltEventForWrapper(built)) + + queueExecutor.awaitExecution() + val sent = events(takeRequest(http)).single().properties() + assertNotNull(sent["\$sdk_debug_session_start"]) + } + + @Test + fun `send skips person processing after the user changed`() { + val sut = getSut(httpServers.mockHttp()) + val identified = assertNotNull(sut.buildEventForWrapper("signup", userProperties = mapOf("plan" to "pro"))) + + sut.reset() + assertNotNull(sut.sendBuiltEventForWrapper(identified)) + + assertEquals(false, assertNotNull(sut.buildEventForWrapper("tap")).options?.get("process_person_profile")) + } } From 55d62cc3df44a339605e85f64d4b9334534e82ce Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Thu, 29 Oct 2026 10:28:42 -0400 Subject: [PATCH 7/9] fix: don't restore a wrapper screen name after a reset since the build --- posthog/src/main/java/com/posthog/PostHog.kt | 3 ++- .../test/java/com/posthog/PostHogWrapperEventTest.kt | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/posthog/src/main/java/com/posthog/PostHog.kt b/posthog/src/main/java/com/posthog/PostHog.kt index c948ecfc0..58f51fe97 100644 --- a/posthog/src/main/java/com/posthog/PostHog.kt +++ b/posthog/src/main/java/com/posthog/PostHog.kt @@ -1483,7 +1483,8 @@ public class PostHog private constructor( properties["\$set_once"] as? Map, ) } - if (!ai && event.event == PostHogEventName.SCREEN.event) { + // A reset() since the build cleared the screen name, so the old one isn't restored. + if (!ai && build.resetGeneration == resetGeneration && event.event == PostHogEventName.SCREEN.event) { (properties["\$screen_name"] as? String)?.trim()?.takeIf { it.isNotEmpty() }?.let { cacheScreenName(it) } } // The build added only the required replay debug keys. The window is claimed here, so a diff --git a/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt b/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt index 87396f6cb..e86d7067f 100644 --- a/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt +++ b/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt @@ -246,6 +246,18 @@ internal class PostHogWrapperEventTest { assertEquals(mapOf("plan" to "pro"), prefs.getValue(PERSON_PROPERTIES_FOR_FLAGS)) } + @Test + fun `send after a reset delivers the screen without restoring its name`() { + val sut = getSut(httpServers.mockHttp()) + val screen = assertNotNull(sut.buildEventForWrapper("\$screen", properties = mapOf("\$screen_name" to "Home"))) + + sut.reset() + assertNotNull(sut.sendBuiltEventForWrapper(screen)) + val next = assertNotNull(sut.buildEventForWrapper("tap")) + + assertNull(next.properties?.get("\$screen_name")) + } + @Test fun `the replay debug bundle is claimed at send`() { val http = httpServers.mockHttp() From e74d3e44b03da837af039bbc3b6d6ba0f4741708 Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Thu, 29 Oct 2026 10:28:58 -0400 Subject: [PATCH 8/9] fix: add the replay debug bundle to wrapper AI events, like captureAi --- posthog/src/main/java/com/posthog/PostHog.kt | 4 ++-- posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/posthog/src/main/java/com/posthog/PostHog.kt b/posthog/src/main/java/com/posthog/PostHog.kt index 58f51fe97..c23ffa0eb 100644 --- a/posthog/src/main/java/com/posthog/PostHog.kt +++ b/posthog/src/main/java/com/posthog/PostHog.kt @@ -1398,7 +1398,7 @@ public class PostHog private constructor( built.uuid?.let { uuid -> // Snapshotted now, so a new session during the round trip doesn't change them. val deferredReplayDebug = - if (!ai && isReplayDebugEvent(event)) { + if (isReplayDebugEvent(event)) { sdkDebugProperties(built.properties?.get("\$session_id") as? String, built.timestamp) { true } .filterKeys { it !in UNTHROTTLED_DEBUG_PROPERTY_KEYS } .takeIf { it.isNotEmpty() } @@ -1491,7 +1491,7 @@ public class PostHog private constructor( // built event the wrapper drops never holds or starts it. Eligibility and values come from // the build, since the wrapper's hook can rename the event and the session can change. val deferredReplayDebug = build.deferredReplayDebug - if (!ai && deferredReplayDebug != null && claimReplayDebugPropertiesWindow()) { + if (deferredReplayDebug != null && claimReplayDebugPropertiesWindow()) { claimedReplayDebugBundle = true // Only keys the event lacks, so the wrapper's values win. deferredReplayDebug.forEach { (key, value) -> diff --git a/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt b/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt index e86d7067f..b9ae1da7f 100644 --- a/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt +++ b/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt @@ -145,12 +145,15 @@ internal class PostHogWrapperEventTest { fun `send on the AI lane posts to the AI endpoint`() { val http = httpServers.mockHttp() val sut = getSut(http) + sut.startSession() val built = assertNotNull(sut.buildEventForWrapper("\$ai_generation", ai = true)) assertEquals(built.uuid, sut.sendBuiltEventForWrapper(built, ai = true)) aiExecutor.awaitExecution() - assertEquals("/i/v1/ai/events", takeRequest(http).path) + val request = takeRequest(http) + assertEquals("/i/v1/ai/events", request.path) + assertNotNull(events(request).single().properties()["\$sdk_debug_session_start"]) } @Test From f93ffbc0cfdd478604bafeb57fe1f2709e8b25fe Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Thu, 29 Oct 2026 17:15:55 -0400 Subject: [PATCH 9/9] fix: read replay debug state once per wrapper build The build's required replay keys and the deferred bundle came from two debugProperties() reads, so a replay transition between them could send $recording_status=buffering without its hold reason. Co-Authored-By: Claude Opus 5.5 --- posthog/src/main/java/com/posthog/PostHog.kt | 23 ++++++++++++++--- .../PostHogSessionReplayHandlerFake.kt | 5 +++- .../com/posthog/PostHogWrapperEventTest.kt | 25 +++++++++++++++++++ 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/posthog/src/main/java/com/posthog/PostHog.kt b/posthog/src/main/java/com/posthog/PostHog.kt index c23ffa0eb..58aeb49cb 100644 --- a/posthog/src/main/java/com/posthog/PostHog.kt +++ b/posthog/src/main/java/com/posthog/PostHog.kt @@ -796,6 +796,7 @@ public class PostHog private constructor( appendGroups: Boolean = true, timestamp: Date? = null, claimReplayDebugBundle: () -> Boolean = { false }, + readReplayDebugProperties: () -> Map = ::readReplayDebugProperties, ): MutableMap { val props = mutableMapOf() @@ -864,7 +865,7 @@ public class PostHog private constructor( if (appendSharedProps) { // After session resolution so the debug snapshot never precedes a rotation triggered by // getActiveSessionId() above. - props.putAll(sdkDebugProperties(sessionIdString, timestamp, claimReplayDebugBundle)) + props.putAll(sdkDebugProperties(sessionIdString, timestamp, claimReplayDebugBundle, readReplayDebugProperties)) // Registered super properties win over everything the SDK set above. val registeredPrefs = getPreferences().getAll() @@ -905,10 +906,14 @@ public class PostHog private constructor( return props } + private fun readReplayDebugProperties(): Map = + sessionReplayHandler?.debugProperties() ?: mapOf("\$recording_status" to "disabled") + private fun sdkDebugProperties( sessionId: String?, timestamp: Date?, claimReplayDebugBundle: () -> Boolean, + readReplayDebugProperties: () -> Map = ::readReplayDebugProperties, ): Map { val props = mutableMapOf() val managerStart = PostHogSessionManager.getSessionStartedAt() @@ -921,7 +926,7 @@ public class PostHog private constructor( // Guarded separately from the session and queue keys below: those come from the session // manager and the queue, not from replay, so a handler that throws must not suppress them. try { - val replayProps = sessionReplayHandler?.debugProperties() ?: mapOf("\$recording_status" to "disabled") + val replayProps = readReplayDebugProperties() if (claimed) { props.putAll(replayProps) } else { @@ -1207,6 +1212,7 @@ public class PostHog private constructor( options: Map?, aiLane: Boolean, claimReplayDebugBundle: () -> Boolean, + readReplayDebugProperties: () -> Map = ::readReplayDebugProperties, setFlagPersonProperties: Boolean = true, deferPersonProcessing: Boolean = false, ): PreparedEvent? { @@ -1284,6 +1290,7 @@ public class PostHog private constructor( appendGroups = !groupIdentify, timestamp = timestamp, claimReplayDebugBundle = { isReplayDebugEvent(event) && claimReplayDebugBundle() }, + readReplayDebugProperties = readReplayDebugProperties, ) val eventProperties = minimizeIfFeatureFlagCalled(event, mergedProperties) @@ -1380,6 +1387,10 @@ public class PostHog private constructor( } else { properties } + // One replay read for the build's required keys and the deferred bundle, so a replay + // transition between them can't pair keys from two different states. + val replayDebugSnapshot by lazy { runCatching { readReplayDebugProperties() } } + val readReplayDebugSnapshot = { replayDebugSnapshot.getOrThrow() } val built = prepareEvent( event, @@ -1392,6 +1403,7 @@ public class PostHog private constructor( options, ai, claimReplayDebugBundle = { false }, + readReplayDebugProperties = readReplayDebugSnapshot, setFlagPersonProperties = false, deferPersonProcessing = true, )?.event ?: return null @@ -1399,7 +1411,12 @@ public class PostHog private constructor( // Snapshotted now, so a new session during the round trip doesn't change them. val deferredReplayDebug = if (isReplayDebugEvent(event)) { - sdkDebugProperties(built.properties?.get("\$session_id") as? String, built.timestamp) { true } + sdkDebugProperties( + built.properties?.get("\$session_id") as? String, + built.timestamp, + claimReplayDebugBundle = { true }, + readReplayDebugProperties = readReplayDebugSnapshot, + ) .filterKeys { it !in UNTHROTTLED_DEBUG_PROPERTY_KEYS } .takeIf { it.isNotEmpty() } } else { diff --git a/posthog/src/test/java/com/posthog/PostHogSessionReplayHandlerFake.kt b/posthog/src/test/java/com/posthog/PostHogSessionReplayHandlerFake.kt index 060ab3854..117e5ac59 100644 --- a/posthog/src/test/java/com/posthog/PostHogSessionReplayHandlerFake.kt +++ b/posthog/src/test/java/com/posthog/PostHogSessionReplayHandlerFake.kt @@ -49,11 +49,14 @@ public class PostHogSessionReplayHandlerFake(private var isActive: Boolean) : Po return isActive } + // runs after each debugProperties() read, so a test can change the state between reads + public var onDebugProperties: (() -> Unit)? = null + override fun debugProperties(): Map { if (throwOnDebugProperties) { throw RuntimeException("debugProperties failed") } - return debugProperties + return debugProperties.also { onDebugProperties?.invoke() } } override fun onEvent( diff --git a/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt b/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt index b9ae1da7f..00ca720c9 100644 --- a/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt +++ b/posthog/src/test/java/com/posthog/PostHogWrapperEventTest.kt @@ -391,6 +391,31 @@ internal class PostHogWrapperEventTest { assertNotNull(sent["\$sdk_debug_session_start"]) } + @Test + fun `build reads the replay debug state once for the required and deferred keys`() { + val http = httpServers.mockHttp() + val replay = + PostHogSessionReplayHandlerFake(true).apply { + debugProperties = + mapOf( + "\$recording_status" to "buffering", + "\$sdk_debug_replay_flush_hold_reason" to "below_minimum_duration", + ) + // A replay stop between two reads would pair buffering with no hold reason. + onDebugProperties = { debugProperties = mapOf("\$recording_status" to "disabled") } + } + val sut = getSut(http) { addIntegration(replay) } + sut.startSession() + + val screen = assertNotNull(sut.buildEventForWrapper("\$screen", properties = mapOf("\$screen_name" to "Home"))) + assertNotNull(sut.sendBuiltEventForWrapper(screen)) + + queueExecutor.awaitExecution() + val sent = events(takeRequest(http)).single().properties() + assertEquals("buffering", sent["\$recording_status"]) + assertEquals("below_minimum_duration", sent["\$sdk_debug_replay_flush_hold_reason"]) + } + @Test fun `send skips person processing after the user changed`() { val sut = getSut(httpServers.mockHttp())