Skip to content

Commit be362ce

Browse files
Shahroz16claude
andcommitted
feat: add LocationPlugin for identify enrichment and event filtering
- Enrich identify events with last known lat/lng in context so Customer.io knows user location at identification time. - Block consumer-sent "Location Update" track events to prevent backend flooding. Only SDK-internal location events pass through using an internal marker that is stripped before delivery. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7855636 commit be362ce

File tree

2 files changed

+83
-1
lines changed

2 files changed

+83
-1
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package io.customer.datapipelines.plugins
2+
3+
import com.segment.analytics.kotlin.core.Analytics
4+
import com.segment.analytics.kotlin.core.BaseEvent
5+
import com.segment.analytics.kotlin.core.IdentifyEvent
6+
import com.segment.analytics.kotlin.core.TrackEvent
7+
import com.segment.analytics.kotlin.core.platform.EventPlugin
8+
import com.segment.analytics.kotlin.core.platform.Plugin
9+
import com.segment.analytics.kotlin.core.utilities.putInContext
10+
import io.customer.sdk.communication.Event
11+
import io.customer.sdk.core.util.Logger
12+
import io.customer.sdk.util.EventNames
13+
import kotlinx.serialization.json.JsonPrimitive
14+
import kotlinx.serialization.json.buildJsonObject
15+
16+
/**
17+
* Plugin that handles location-related event processing:
18+
*
19+
* 1. Enriches identify events with the last known location in context,
20+
* so Customer.io knows where the user is when their profile is identified.
21+
*
22+
* 2. Blocks consumer-sent "Location Update" track events to prevent flooding
23+
* the backend. Only SDK-internal location events (marked with [INTERNAL_LOCATION_KEY])
24+
* are allowed through; the marker is stripped before the event reaches the destination.
25+
*/
26+
internal class LocationPlugin(private val logger: Logger) : EventPlugin {
27+
override val type: Plugin.Type = Plugin.Type.Enrichment
28+
override lateinit var analytics: Analytics
29+
30+
@Volatile
31+
internal var lastLocation: Event.LocationData? = null
32+
33+
override fun identify(payload: IdentifyEvent): BaseEvent {
34+
val location = lastLocation ?: return payload
35+
payload.putInContext(
36+
"location",
37+
buildJsonObject {
38+
put("latitude", JsonPrimitive(location.latitude))
39+
put("longitude", JsonPrimitive(location.longitude))
40+
}
41+
)
42+
return payload
43+
}
44+
45+
override fun track(payload: TrackEvent): BaseEvent? {
46+
if (payload.event != EventNames.LOCATION_UPDATE) {
47+
return payload
48+
}
49+
50+
// Check for the internal marker that only the SDK sets
51+
val isInternal = payload.properties[INTERNAL_LOCATION_KEY]?.let {
52+
(it as? JsonPrimitive)?.content?.toBooleanStrictOrNull() == true
53+
} ?: false
54+
55+
if (!isInternal) {
56+
logger.debug("Blocking consumer-sent \"${EventNames.LOCATION_UPDATE}\" event. Location events are managed by the SDK.")
57+
return null
58+
}
59+
60+
// Strip the internal marker before sending to destination
61+
payload.properties = buildJsonObject {
62+
payload.properties.forEach { (key, value) ->
63+
if (key != INTERNAL_LOCATION_KEY) {
64+
put(key, value)
65+
}
66+
}
67+
}
68+
return payload
69+
}
70+
71+
companion object {
72+
internal const val INTERNAL_LOCATION_KEY = "_cio_internal_location"
73+
}
74+
}

datapipelines/src/main/kotlin/io/customer/sdk/CustomerIO.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import io.customer.datapipelines.plugins.AutomaticActivityScreenTrackingPlugin
2525
import io.customer.datapipelines.plugins.AutomaticApplicationLifecycleTrackingPlugin
2626
import io.customer.datapipelines.plugins.ContextPlugin
2727
import io.customer.datapipelines.plugins.CustomerIODestination
28+
import io.customer.datapipelines.plugins.LocationPlugin
2829
import io.customer.datapipelines.plugins.ScreenFilterPlugin
2930
import io.customer.sdk.communication.Event
3031
import io.customer.sdk.communication.subscribe
@@ -107,6 +108,7 @@ class CustomerIO private constructor(
107108
)
108109

109110
private val contextPlugin: ContextPlugin = ContextPlugin(deviceStore)
111+
private val locationPlugin: LocationPlugin = LocationPlugin(logger)
110112

111113
init {
112114
// Set analytics logger and debug logs based on SDK logger configuration
@@ -127,6 +129,7 @@ class CustomerIO private constructor(
127129

128130
// Add plugin to filter events based on SDK configuration
129131
analytics.add(ScreenFilterPlugin(moduleConfig.screenViewUse))
132+
analytics.add(locationPlugin)
130133
analytics.add(ApplicationLifecyclePlugin())
131134

132135
// subscribe to journey events emitted from push/in-app module to send them via data pipelines
@@ -159,11 +162,16 @@ class CustomerIO private constructor(
159162
private fun trackLocation(event: Event.TrackLocationEvent) {
160163
val location = event.location
161164
logger.debug("tracking location update: lat=${location.latitude}, lng=${location.longitude}")
165+
166+
// Cache location for enriching future identify events
167+
locationPlugin.lastLocation = location
168+
162169
track(
163170
name = EventNames.LOCATION_UPDATE,
164171
properties = mapOf(
165172
"lat" to location.latitude,
166-
"lng" to location.longitude
173+
"lng" to location.longitude,
174+
LocationPlugin.INTERNAL_LOCATION_KEY to true
167175
)
168176
)
169177
}

0 commit comments

Comments
 (0)