Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/strong-moments-strive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"posthog-android": minor
Comment thread
havenbarnes marked this conversation as resolved.
"posthog": minor
---

Add support for initiating push notification subscriptions using PostHog Workflows
2 changes: 1 addition & 1 deletion posthog-android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ dependencies {
implementation(kotlin("stdlib-jdk8", PosthogBuildConfig.Kotlin.KOTLIN))
implementation("androidx.lifecycle:lifecycle-process:${PosthogBuildConfig.Dependencies.LIFECYCLE}")
implementation("androidx.lifecycle:lifecycle-common-java8:${PosthogBuildConfig.Dependencies.LIFECYCLE}")
implementation("androidx.core:core:${PosthogBuildConfig.Dependencies.ANDROIDX_CORE}")
implementation("androidx.core:core-ktx:${PosthogBuildConfig.Dependencies.ANDROIDX_CORE}")
implementation("com.squareup.curtains:curtains:${PosthogBuildConfig.Dependencies.CURTAINS}")

// compile only
Expand Down
1 change: 1 addition & 0 deletions posthog-android/gradle.lockfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ androidx.compose.ui:ui-unit:1.0.0=compileOnlyDependenciesMetadata,debugCompileCl
androidx.compose.ui:ui:1.0.0=compileOnlyDependenciesMetadata,debugCompileClasspath,releaseCompileClasspath
androidx.concurrent:concurrent-futures-ktx:1.1.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath,testImplementationDependenciesMetadata
androidx.concurrent:concurrent-futures:1.1.0=debugAndroidTestRuntimeClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath,testImplementationDependenciesMetadata
androidx.core:core-ktx:1.5.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
androidx.core:core:1.5.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
androidx.lifecycle:lifecycle-common-java8:2.6.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
androidx.lifecycle:lifecycle-common:2.3.1=testImplementationDependenciesMetadata
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import android.telephony.TelephonyManager
import android.util.Base64
import android.util.DisplayMetrics
import android.view.WindowManager
import androidx.core.net.toUri
import com.posthog.PostHogInternal
import com.posthog.android.PostHogAndroidConfig
import java.io.ByteArrayOutputStream
Expand Down Expand Up @@ -178,7 +179,7 @@ internal fun Intent.getReferrerInfo(config: PostHogAndroidConfig): Map<String, S

internal fun String.tryParse(config: PostHogAndroidConfig): Uri? {
return try {
Uri.parse(this)
this.toUri()
} catch (e: Throwable) {
config.logger.log("Error parsing string: $this. Exception: $e.")
null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ public class PostHogFake : PostHogInterface {
public var properties: Map<String, Any>? = null
public var captures: Int = 0
public var flushes: Int = 0
public var pushDeviceToken: String? = null
public var pushAppId: String? = null
public var pushPlatform: String? = null
public var pushRegistrations: Int = 0

override fun <T : PostHogConfig> setup(config: T) {
}
Expand Down Expand Up @@ -202,6 +206,17 @@ public class PostHogFake : PostHogInterface {
return null
}

override fun registerPushNotificationToken(
deviceToken: String,
appId: String,
platform: String,
) {
pushDeviceToken = deviceToken
pushAppId = appId
pushPlatform = platform
pushRegistrations++
}

override fun <T : PostHogConfig> getConfig(): T? {
return null
}
Expand Down
5 changes: 5 additions & 0 deletions posthog/api/posthog.api
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public final class com/posthog/PostHog : com/posthog/PostHogStateless, com/posth
public fun optIn ()V
public fun optOut ()V
public fun register (Ljava/lang/String;Ljava/lang/Object;)V
public fun registerPushNotificationToken (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public fun reloadFeatureFlags (Lcom/posthog/PostHogOnFeatureFlags;)V
public fun reset ()V
public fun resetGroupPropertiesForFlags (Ljava/lang/String;Z)V
Expand Down Expand Up @@ -91,6 +92,7 @@ public final class com/posthog/PostHog$Companion : com/posthog/PostHogInterface
public fun optOut ()V
public final fun overrideSharedInstance (Lcom/posthog/PostHogInterface;)V
public fun register (Ljava/lang/String;Ljava/lang/Object;)V
public fun registerPushNotificationToken (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public fun reloadFeatureFlags (Lcom/posthog/PostHogOnFeatureFlags;)V
public fun reset ()V
public fun resetGroupPropertiesForFlags (Ljava/lang/String;Z)V
Expand Down Expand Up @@ -328,6 +330,7 @@ public abstract interface class com/posthog/PostHogInterface : com/posthog/PostH
public abstract fun isSessionActive ()Z
public abstract fun isSessionReplayActive ()Z
public abstract fun register (Ljava/lang/String;Ljava/lang/Object;)V
public abstract fun registerPushNotificationToken (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public abstract fun reloadFeatureFlags (Lcom/posthog/PostHogOnFeatureFlags;)V
public abstract fun reset ()V
public abstract fun resetGroupPropertiesForFlags (Ljava/lang/String;Z)V
Expand All @@ -352,6 +355,7 @@ public final class com/posthog/PostHogInterface$DefaultImpls {
public static synthetic fun getFeatureFlagResult$default (Lcom/posthog/PostHogInterface;Ljava/lang/String;Ljava/lang/Boolean;ILjava/lang/Object;)Lcom/posthog/FeatureFlagResult;
public static synthetic fun group$default (Lcom/posthog/PostHogInterface;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)V
public static synthetic fun isFeatureEnabled$default (Lcom/posthog/PostHogInterface;Ljava/lang/String;ZLjava/lang/Boolean;ILjava/lang/Object;)Z
public static synthetic fun registerPushNotificationToken$default (Lcom/posthog/PostHogInterface;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)V
public static synthetic fun reloadFeatureFlags$default (Lcom/posthog/PostHogInterface;Lcom/posthog/PostHogOnFeatureFlags;ILjava/lang/Object;)V
public static synthetic fun resetGroupPropertiesForFlags$default (Lcom/posthog/PostHogInterface;Ljava/lang/String;ZILjava/lang/Object;)V
public static synthetic fun resetPersonPropertiesForFlags$default (Lcom/posthog/PostHogInterface;ZILjava/lang/Object;)V
Expand Down Expand Up @@ -646,6 +650,7 @@ public final class com/posthog/internal/PostHogApi {
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;ILjava/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 final fun remoteConfig ()Lcom/posthog/internal/PostHogRemoteConfigResponse;
public final fun snapshot (Ljava/util/List;)V
}
Expand Down
53 changes: 53 additions & 0 deletions posthog/src/main/java/com/posthog/PostHog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.posthog.internal.PostHogPreferences.Companion.OPT_OUT
import com.posthog.internal.PostHogPreferences.Companion.PERSON_PROCESSING
import com.posthog.internal.PostHogPreferences.Companion.VERSION
import com.posthog.internal.PostHogPrintLogger
import com.posthog.internal.PostHogPushSubscriptionManager
import com.posthog.internal.PostHogQueueInterface
import com.posthog.internal.PostHogRemoteConfig
import com.posthog.internal.PostHogSendCachedEventsIntegration
Expand Down Expand Up @@ -63,6 +64,8 @@ public class PostHog private constructor(
private val cachedPersonPropertiesLock = Any()

private var replayQueue: PostHogQueueInterface? = null
private var api: PostHogApi? = null
private var pushSubscriptionManager: PostHogPushSubscriptionManager? = null

private val remoteConfig: PostHogRemoteConfig?
get() = config?.remoteConfigHolder
Expand Down Expand Up @@ -169,8 +172,10 @@ public class PostHog private constructor(
)

this.config = config
this.api = api
this.queue = queue
this.replayQueue = replayQueue
this.pushSubscriptionManager = PostHogPushSubscriptionManager(config, api, queueExecutor)

if (featureFlags is PostHogRemoteConfig) {
config.remoteConfigHolder = featureFlags
Expand All @@ -190,6 +195,8 @@ public class PostHog private constructor(

queue.start()

pushSubscriptionManager?.retryPending()

startSession()

config.integrations.forEach {
Expand Down Expand Up @@ -305,6 +312,9 @@ public class PostHog private constructor(

queue?.stop()
replayQueue?.stop()
api = null
pushSubscriptionManager?.close()
pushSubscriptionManager = null

featureFlagsCalled.clear()

Expand Down Expand Up @@ -1353,6 +1363,41 @@ public class PostHog private constructor(
return PostHogSessionManager.isSessionActive()
}

override fun registerPushNotificationToken(
deviceToken: String,
appId: String,
platform: String,
) {
if (!isEnabled()) {
return
}
if (config?.optOut == true) {
config?.logger?.log("PostHog is in OptOut state.")
return
}
if (deviceToken.isBlank()) {
config?.logger?.log("registerPushNotificationToken call not allowed, deviceToken is blank.")
return
}
if (appId.isBlank()) {
config?.logger?.log("registerPushNotificationToken call not allowed, appId is blank.")
return
}

val currentDistinctId = distinctId
if (currentDistinctId.isBlank()) {
config?.logger?.log("registerPushNotificationToken call not allowed, distinctId is invalid.")
return
}

pushSubscriptionManager?.register(
distinctId = currentDistinctId,
deviceToken = deviceToken,
appId = appId,
platform = platform,
)
}

override fun <T : PostHogConfig> getConfig(): T? {
@Suppress("UNCHECKED_CAST")
return super<PostHogStateless>.config as? T
Expand Down Expand Up @@ -1714,5 +1759,13 @@ public class PostHog private constructor(
override fun getSessionId(): UUID? {
return shared.getSessionId()
}

override fun registerPushNotificationToken(
deviceToken: String,
appId: String,
platform: String,
) {
shared.registerPushNotificationToken(deviceToken, appId, platform)
}
}
}
15 changes: 15 additions & 0 deletions posthog/src/main/java/com/posthog/PostHogInterface.kt
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,21 @@ public interface PostHogInterface : PostHogCoreInterface {
flagVariant: String? = null,
)

/**
* Registers a push notification device token with PostHog.
* This sends the token to the PostHog push subscriptions API so that
* push notifications can be delivered to this device.
*
* @param deviceToken the device push token (e.g. FCM registration token)
* @param appId the app identifier - Firebase project_id for Android, APNS bundle_id for iOS
* @param platform the platform, defaults to "android"
*/
public fun registerPushNotificationToken(
deviceToken: String,
appId: String,
platform: String = "android",
)

@PostHogInternal
public fun <T : PostHogConfig> getConfig(): T?
}
35 changes: 35 additions & 0 deletions posthog/src/main/java/com/posthog/internal/PostHogApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,41 @@ public class PostHogApi(
}
}

@Throws(PostHogApiError::class, IOException::class)
public fun pushSubscription(
distinctId: String,
deviceToken: String,
platform: String,
appId: String,
) {
val request =
PostHogPushSubscriptionRequest(
apiKey = config.apiKey,
distinctId = distinctId,
deviceToken = deviceToken,
platform = platform,
appId = appId,
)

val url = "$theHost/api/push_subscriptions/"
logRequest(request, url)

val httpRequest =
makeRequest(url) {
config.serializer.serialize(request, it.bufferedWriter())
}

logRequestHeaders(httpRequest)

client.newCall(httpRequest).execute().use {
val response = logResponse(it)

if (!response.isSuccessful) {
throw PostHogApiError(response.code, response.message, response.body)
}
}
}

private fun logResponse(response: Response): Response {
if (config.debug) {
try {
Expand Down
Loading
Loading