Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
29 changes: 0 additions & 29 deletions core/api/core.api
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,6 @@ public final class io/customer/sdk/communication/Event$DeleteDeviceTokenEvent :
public fun <init> ()V
}

public final class io/customer/sdk/communication/Event$LocationData {
public fun <init> (DD)V
public final fun component1 ()D
public final fun component2 ()D
public final fun copy (DD)Lio/customer/sdk/communication/Event$LocationData;
public static synthetic fun copy$default (Lio/customer/sdk/communication/Event$LocationData;DDILjava/lang/Object;)Lio/customer/sdk/communication/Event$LocationData;
public fun equals (Ljava/lang/Object;)Z
public final fun getLatitude ()D
public final fun getLongitude ()D
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class io/customer/sdk/communication/Event$RegisterDeviceTokenEvent : io/customer/sdk/communication/Event {
public fun <init> (Ljava/lang/String;)V
public final fun component1 ()Ljava/lang/String;
Expand Down Expand Up @@ -71,17 +58,6 @@ public final class io/customer/sdk/communication/Event$TrackInAppMetricEvent : i
public fun toString ()Ljava/lang/String;
}

public final class io/customer/sdk/communication/Event$TrackLocationEvent : io/customer/sdk/communication/Event {
public fun <init> (Lio/customer/sdk/communication/Event$LocationData;)V
public final fun component1 ()Lio/customer/sdk/communication/Event$LocationData;
public final fun copy (Lio/customer/sdk/communication/Event$LocationData;)Lio/customer/sdk/communication/Event$TrackLocationEvent;
public static synthetic fun copy$default (Lio/customer/sdk/communication/Event$TrackLocationEvent;Lio/customer/sdk/communication/Event$LocationData;ILjava/lang/Object;)Lio/customer/sdk/communication/Event$TrackLocationEvent;
public fun equals (Ljava/lang/Object;)Z
public final fun getLocation ()Lio/customer/sdk/communication/Event$LocationData;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class io/customer/sdk/communication/Event$TrackPushMetricEvent : io/customer/sdk/communication/Event {
public fun <init> (Ljava/lang/String;Lio/customer/sdk/events/Metric;Ljava/lang/String;)V
public final fun component1 ()Ljava/lang/String;
Expand Down Expand Up @@ -128,11 +104,6 @@ public final class io/customer/sdk/communication/EventBusImpl : io/customer/sdk/
public fun subscribe (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job;
}

public abstract interface class io/customer/sdk/communication/LocationCache {
public abstract fun getLastLocation ()Lio/customer/sdk/communication/Event$LocationData;
public abstract fun setLastLocation (Lio/customer/sdk/communication/Event$LocationData;)V
}

public final class io/customer/sdk/core/BuildConfig {
public static final field BUILD_TYPE Ljava/lang/String;
public static final field DEBUG Z
Expand Down
20 changes: 0 additions & 20 deletions core/src/main/kotlin/io/customer/sdk/communication/Event.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,24 +51,4 @@ sealed class Event {
) : Event()

class DeleteDeviceTokenEvent : Event()

/**
* Event emitted when a new location is available.
* Published by the Location module on every location update.
* DataPipelines applies the userId gate and sync filter (24h + 1km)
* before sending to the server.
*/
data class TrackLocationEvent(
val location: LocationData
) : Event()

/**
* Location data in a framework-agnostic format.
* Used to pass location information between modules without
* requiring Android location framework imports.
*/
data class LocationData(
val latitude: Double,
val longitude: Double
)
}

This file was deleted.

17 changes: 17 additions & 0 deletions core/src/main/kotlin/io/customer/sdk/core/pipeline/DataPipeline.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.customer.sdk.core.pipeline

import io.customer.base.internal.InternalCustomerIOApi

/**
* Abstraction for sending track events to the data pipeline.
*
* Modules retrieve an implementation via `SDKComponent.getOrNull<DataPipeline>()`
* to send events directly without going through EventBus.
*
* This is an internal SDK contract — not intended for use by host app developers.
*/
@InternalCustomerIOApi
interface DataPipeline {
val isUserIdentified: Boolean
fun track(name: String, properties: Map<String, Any?>)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.customer.sdk.core.pipeline

import io.customer.base.internal.InternalCustomerIOApi

/**
* Interface for modules that contribute context entries to identify events.
*
* Implementations return a map of primitive-valued entries (String, Number, Boolean)
* that will be added to the identify event's context via `putInContext()`.
* Return an empty map when there is nothing to contribute.
*
* These are NOT profile traits/attributes — they are context-level enrichment
* data (e.g., location coordinates) attached to the identify event payload.
*
* This is an internal SDK contract — not intended for use by host app developers.
*/
@InternalCustomerIOApi
interface IdentifyContextProvider {
fun getIdentifyContext(): Map<String, Any>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.customer.sdk.core.pipeline

import io.customer.base.internal.InternalCustomerIOApi
import io.customer.sdk.core.di.SDKComponent

/**
* Thread-safe registry of [IdentifyContextProvider] instances.
*
* Modules register providers during initialization. The datapipelines module
* queries all providers when enriching identify event context.
*
* Cleared automatically when [SDKComponent.reset] clears singletons.
*
* This is an internal SDK contract — not intended for use by host app developers.
*/
@InternalCustomerIOApi
class IdentifyContextRegistry {
private val providers = mutableListOf<IdentifyContextProvider>()

@Synchronized
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure that @Synchronized is the right tool here? Do plugins use threads or coroutines?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plugins run synchronously on the caller's thread, EventPlugin.identify() is blocking and executes on whatever thread called analytics.identify(). No coroutines involved at the plugin level.

@Synchronized is the right tool here because register(), getAll(), and clear() are fast, non-suspending operations (list add/copy/clear). A coroutine Mutex would require these to be suspend functions, which doesn't fit the EventPlugin interface.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main idea is that the registry is called from other modules to register themselves and this could expand to more modules in the future. So we can't be sure that this invariant remains true

fun register(provider: IdentifyContextProvider) {
if (provider !in providers) {
providers.add(provider)
}
}

@Synchronized
fun getAll(): List<IdentifyContextProvider> = providers.toList()

@Synchronized
fun clear() {
providers.clear()
}
}

/**
* Singleton accessor for [IdentifyContextRegistry] via [SDKComponent].
*/
@InternalCustomerIOApi
val SDKComponent.identifyContextRegistry: IdentifyContextRegistry
get() = singleton { IdentifyContextRegistry() }
3 changes: 2 additions & 1 deletion datapipelines/api/datapipelines.api
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public final class io/customer/datapipelines/plugins/StringExtensionsKt {
public static final fun getScreenNameFromActivity (Ljava/lang/String;)Ljava/lang/String;
}

public final class io/customer/sdk/CustomerIO : io/customer/sdk/DataPipelineInstance, io/customer/sdk/core/module/CustomerIOModule {
public final class io/customer/sdk/CustomerIO : io/customer/sdk/DataPipelineInstance, io/customer/sdk/core/module/CustomerIOModule, io/customer/sdk/core/pipeline/DataPipeline {
public static final field Companion Lio/customer/sdk/CustomerIO$Companion;
public synthetic fun <init> (Lio/customer/sdk/core/di/AndroidSDKComponent;Lio/customer/datapipelines/config/DataPipelinesModuleConfig;Lcom/segment/analytics/kotlin/core/Analytics;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun getAnonymousId ()Ljava/lang/String;
Expand All @@ -144,6 +144,7 @@ public final class io/customer/sdk/CustomerIO : io/customer/sdk/DataPipelineInst
public fun initialize ()V
public static final fun initialize (Lio/customer/sdk/CustomerIOConfig;)V
public static final fun instance ()Lio/customer/sdk/CustomerIO;
public fun isUserIdentified ()Z
public fun setDeviceAttributes (Ljava/util/Map;)V
public fun setDeviceAttributesDeprecated (Ljava/util/Map;)V
public fun setProfileAttributes (Ljava/util/Map;)V
Expand Down
9 changes: 9 additions & 0 deletions datapipelines/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import io.customer.android.Configurations
import io.customer.android.Dependencies
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
id 'com.android.library'
Expand Down Expand Up @@ -32,6 +33,14 @@ android {
}
}

tasks.withType(KotlinCompile).all {
kotlinOptions {
freeCompilerArgs += [
'-opt-in=io.customer.base.internal.InternalCustomerIOApi',
]
}
}

dependencies {
api project(":base")
api project(":core")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.customer.datapipelines.plugins

import com.segment.analytics.kotlin.core.Analytics
import com.segment.analytics.kotlin.core.BaseEvent
import com.segment.analytics.kotlin.core.IdentifyEvent
import com.segment.analytics.kotlin.core.platform.EventPlugin
import com.segment.analytics.kotlin.core.platform.Plugin
import com.segment.analytics.kotlin.core.utilities.putInContext
import io.customer.sdk.core.pipeline.IdentifyContextRegistry
import io.customer.sdk.core.util.Logger
import kotlinx.serialization.json.JsonPrimitive

/**
* Generic Segment enrichment plugin that queries all registered
* [IdentifyContextProvider][io.customer.sdk.core.pipeline.IdentifyContextProvider]
* instances and adds their entries to the identify event context.
*
* This plugin has zero knowledge of specific modules — providers
* manage their own state and return primitive-valued maps.
*/
internal class IdentifyContextPlugin(
private val registry: IdentifyContextRegistry,
private val logger: Logger
) : EventPlugin {
override val type: Plugin.Type = Plugin.Type.Enrichment
override lateinit var analytics: Analytics

override fun identify(payload: IdentifyEvent): BaseEvent {
for (provider in registry.getAll()) {
try {
val context = provider.getIdentifyContext()
if (context.isEmpty()) continue
for ((key, value) in context) {
val jsonValue = when (value) {
is String -> JsonPrimitive(value)
is Number -> JsonPrimitive(value)
is Boolean -> JsonPrimitive(value)
else -> {
logger.debug("Skipping non-primitive context entry: $key")
continue
}
}
payload.putInContext(key, jsonValue)
}
} catch (e: Exception) {
logger.error("IdentifyContextProvider failed: ${e.message}")
}
}
return payload
}
}

This file was deleted.

Loading
Loading