Skip to content

Commit bfcbb80

Browse files
authored
feat: in-app anonymous support (#614)
1 parent 3f0d75f commit bfcbb80

File tree

25 files changed

+1324
-113
lines changed

25 files changed

+1324
-113
lines changed

core/api/core.api

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@ public abstract class io/customer/sdk/communication/Event {
1212
public fun getTimestamp ()Ljava/util/Date;
1313
}
1414

15+
public final class io/customer/sdk/communication/Event$AnonymousIdGeneratedEvent : io/customer/sdk/communication/Event {
16+
public fun <init> (Ljava/lang/String;)V
17+
public final fun component1 ()Ljava/lang/String;
18+
public final fun copy (Ljava/lang/String;)Lio/customer/sdk/communication/Event$AnonymousIdGeneratedEvent;
19+
public static synthetic fun copy$default (Lio/customer/sdk/communication/Event$AnonymousIdGeneratedEvent;Ljava/lang/String;ILjava/lang/Object;)Lio/customer/sdk/communication/Event$AnonymousIdGeneratedEvent;
20+
public fun equals (Ljava/lang/Object;)Z
21+
public final fun getAnonymousId ()Ljava/lang/String;
22+
public fun hashCode ()I
23+
public fun toString ()Ljava/lang/String;
24+
}
25+
1526
public final class io/customer/sdk/communication/Event$DeleteDeviceTokenEvent : io/customer/sdk/communication/Event {
1627
public fun <init> ()V
1728
}

core/src/main/kotlin/io/customer/sdk/communication/Event.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ sealed class Event {
2121
val identifier: String
2222
) : Event()
2323

24+
data class AnonymousIdGeneratedEvent(
25+
val anonymousId: String
26+
) : Event()
27+
2428
data class ScreenViewedEvent(
2529
val name: String
2630
) : Event()

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,19 @@ class CustomerIO private constructor(
129129

130130
// subscribe to journey events emitted from push/in-app module to send them via data pipelines
131131
subscribeToJourneyEvents()
132-
// if profile is already identified, republish identifier for late-added modules.
133-
postProfileAlreadyIdentified()
132+
// republish profile/anonymous events for late-added modules
133+
postUserIdentificationEvents()
134134
}
135135

136-
private fun postProfileAlreadyIdentified() {
137-
analytics.userId()?.let { userId ->
136+
private fun postUserIdentificationEvents() {
137+
val userId = analytics.userId()
138+
if (userId != null) {
138139
eventBus.publish(Event.ProfileIdentifiedEvent(identifier = userId))
140+
} else {
141+
val anonymousId = analytics.anonymousId()
142+
if (anonymousId.isNotBlank()) {
143+
eventBus.publish(Event.AnonymousIdGeneratedEvent(anonymousId = anonymousId))
144+
}
139145
}
140146
}
141147

datapipelines/src/test/java/io/customer/datapipelines/ConcurrentScreenViewTest.kt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import io.customer.sdk.core.di.SDKComponent
1010
import io.mockk.every
1111
import io.mockk.mockk
1212
import io.mockk.slot
13-
import io.mockk.verify
1413
import java.util.concurrent.CountDownLatch
1514
import java.util.concurrent.Executors
1615
import java.util.concurrent.TimeUnit
@@ -106,9 +105,17 @@ class ConcurrentScreenViewTest : JUnitTest() {
106105
executor.awaitTermination(1, TimeUnit.SECONDS)
107106

108107
// Verify that screen method was called the expected number of times
109-
verify(exactly = totalEvents) {
110-
eventBus.publish(any<Event.ScreenViewedEvent>())
108+
assertEquals(totalEvents, capturedEvents.size, "Expected exactly $totalEvents screen events but got ${capturedEvents.size}")
109+
110+
// Verify all events are ScreenViewedEvents with correct names
111+
val expectedScreenNames = mutableSetOf<String>()
112+
repeat(threadCount) { threadId ->
113+
repeat(eventsPerThread) { eventIndex ->
114+
expectedScreenNames.add("Screen_${eventIndex}_Thread_$threadId")
115+
}
111116
}
117+
val actualScreenNames = capturedEvents.map { it.name }.toSet()
118+
assertEquals(expectedScreenNames, actualScreenNames, "Screen names don't match expected set")
112119
}
113120

114121
@Test

messaginginapp/api/messaginginapp.api

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,17 +94,46 @@ public final class io/customer/messaginginapp/gist/data/listeners/Queue : io/cus
9494
public fun logView (Lio/customer/messaginginapp/gist/data/model/Message;)V
9595
}
9696

97+
public final class io/customer/messaginginapp/gist/data/model/BroadcastFrequency {
98+
public fun <init> (IIZ)V
99+
public synthetic fun <init> (IIZILkotlin/jvm/internal/DefaultConstructorMarker;)V
100+
public final fun component1 ()I
101+
public final fun component2 ()I
102+
public final fun component3 ()Z
103+
public final fun copy (IIZ)Lio/customer/messaginginapp/gist/data/model/BroadcastFrequency;
104+
public static synthetic fun copy$default (Lio/customer/messaginginapp/gist/data/model/BroadcastFrequency;IIZILjava/lang/Object;)Lio/customer/messaginginapp/gist/data/model/BroadcastFrequency;
105+
public fun equals (Ljava/lang/Object;)Z
106+
public final fun getCount ()I
107+
public final fun getDelay ()I
108+
public final fun getIgnoreDismiss ()Z
109+
public fun hashCode ()I
110+
public fun toString ()Ljava/lang/String;
111+
}
112+
113+
public final class io/customer/messaginginapp/gist/data/model/BroadcastProperties {
114+
public fun <init> (Lio/customer/messaginginapp/gist/data/model/BroadcastFrequency;)V
115+
public final fun component1 ()Lio/customer/messaginginapp/gist/data/model/BroadcastFrequency;
116+
public final fun copy (Lio/customer/messaginginapp/gist/data/model/BroadcastFrequency;)Lio/customer/messaginginapp/gist/data/model/BroadcastProperties;
117+
public static synthetic fun copy$default (Lio/customer/messaginginapp/gist/data/model/BroadcastProperties;Lio/customer/messaginginapp/gist/data/model/BroadcastFrequency;ILjava/lang/Object;)Lio/customer/messaginginapp/gist/data/model/BroadcastProperties;
118+
public fun equals (Ljava/lang/Object;)Z
119+
public final fun getFrequency ()Lio/customer/messaginginapp/gist/data/model/BroadcastFrequency;
120+
public fun hashCode ()I
121+
public fun toString ()Ljava/lang/String;
122+
}
123+
97124
public final class io/customer/messaginginapp/gist/data/model/GistProperties {
98-
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/customer/messaginginapp/gist/data/model/MessagePosition;ZLjava/lang/String;)V
125+
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/customer/messaginginapp/gist/data/model/MessagePosition;ZLjava/lang/String;Lio/customer/messaginginapp/gist/data/model/BroadcastProperties;)V
99126
public final fun component1 ()Ljava/lang/String;
100127
public final fun component2 ()Ljava/lang/String;
101128
public final fun component3 ()Ljava/lang/String;
102129
public final fun component4 ()Lio/customer/messaginginapp/gist/data/model/MessagePosition;
103130
public final fun component5 ()Z
104131
public final fun component6 ()Ljava/lang/String;
105-
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/customer/messaginginapp/gist/data/model/MessagePosition;ZLjava/lang/String;)Lio/customer/messaginginapp/gist/data/model/GistProperties;
106-
public static synthetic fun copy$default (Lio/customer/messaginginapp/gist/data/model/GistProperties;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/customer/messaginginapp/gist/data/model/MessagePosition;ZLjava/lang/String;ILjava/lang/Object;)Lio/customer/messaginginapp/gist/data/model/GistProperties;
132+
public final fun component7 ()Lio/customer/messaginginapp/gist/data/model/BroadcastProperties;
133+
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/customer/messaginginapp/gist/data/model/MessagePosition;ZLjava/lang/String;Lio/customer/messaginginapp/gist/data/model/BroadcastProperties;)Lio/customer/messaginginapp/gist/data/model/GistProperties;
134+
public static synthetic fun copy$default (Lio/customer/messaginginapp/gist/data/model/GistProperties;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/customer/messaginginapp/gist/data/model/MessagePosition;ZLjava/lang/String;Lio/customer/messaginginapp/gist/data/model/BroadcastProperties;ILjava/lang/Object;)Lio/customer/messaginginapp/gist/data/model/GistProperties;
107135
public fun equals (Ljava/lang/Object;)Z
136+
public final fun getBroadcast ()Lio/customer/messaginginapp/gist/data/model/BroadcastProperties;
108137
public final fun getCampaignId ()Ljava/lang/String;
109138
public final fun getElementId ()Ljava/lang/String;
110139
public final fun getOverlayColor ()Ljava/lang/String;
@@ -138,6 +167,11 @@ public final class io/customer/messaginginapp/gist/data/model/Message {
138167
public fun toString ()Ljava/lang/String;
139168
}
140169

170+
public final class io/customer/messaginginapp/gist/data/model/MessageKt {
171+
public static final fun isMessageAnonymous (Lio/customer/messaginginapp/gist/data/model/Message;)Z
172+
public static final fun isShowAlwaysAnonymous (Lio/customer/messaginginapp/gist/data/model/Message;)Z
173+
}
174+
141175
public final class io/customer/messaginginapp/gist/data/model/MessagePosition : java/lang/Enum {
142176
public static final field BOTTOM Lio/customer/messaginginapp/gist/data/model/MessagePosition;
143177
public static final field CENTER Lio/customer/messaginginapp/gist/data/model/MessagePosition;
@@ -167,6 +201,7 @@ public final class io/customer/messaginginapp/gist/presentation/GistSdk : io/cus
167201
public fun dismissMessage ()V
168202
public fun fetchInAppMessages ()V
169203
public fun reset ()V
204+
public fun setAnonymousId (Ljava/lang/String;)V
170205
public fun setCurrentRoute (Ljava/lang/String;)V
171206
public fun setUserId (Ljava/lang/String;)V
172207
}
@@ -323,6 +358,17 @@ public final class io/customer/messaginginapp/state/InAppMessagingAction$Reset :
323358
public static final field INSTANCE Lio/customer/messaginginapp/state/InAppMessagingAction$Reset;
324359
}
325360

361+
public final class io/customer/messaginginapp/state/InAppMessagingAction$SetAnonymousIdentifier : io/customer/messaginginapp/state/InAppMessagingAction {
362+
public fun <init> (Ljava/lang/String;)V
363+
public final fun component1 ()Ljava/lang/String;
364+
public final fun copy (Ljava/lang/String;)Lio/customer/messaginginapp/state/InAppMessagingAction$SetAnonymousIdentifier;
365+
public static synthetic fun copy$default (Lio/customer/messaginginapp/state/InAppMessagingAction$SetAnonymousIdentifier;Ljava/lang/String;ILjava/lang/Object;)Lio/customer/messaginginapp/state/InAppMessagingAction$SetAnonymousIdentifier;
366+
public fun equals (Ljava/lang/Object;)Z
367+
public final fun getAnonymousId ()Ljava/lang/String;
368+
public fun hashCode ()I
369+
public fun toString ()Ljava/lang/String;
370+
}
371+
326372
public final class io/customer/messaginginapp/state/InAppMessagingAction$SetPageRoute : io/customer/messaginginapp/state/InAppMessagingAction {
327373
public fun <init> (Ljava/lang/String;)V
328374
public final fun component1 ()Ljava/lang/String;
@@ -362,8 +408,20 @@ public final class io/customer/messaginginapp/state/InAppMessagingActionKt {
362408

363409
public abstract interface class io/customer/messaginginapp/store/InAppPreferenceStore {
364410
public abstract fun clearAll ()V
411+
public abstract fun clearAllAnonymousData ()V
412+
public abstract fun clearAnonymousTracking (Ljava/lang/String;)V
413+
public abstract fun getAnonymousMessages ()Ljava/lang/String;
414+
public abstract fun getAnonymousNextShowTime (Ljava/lang/String;)J
415+
public abstract fun getAnonymousTimesShown (Ljava/lang/String;)I
365416
public abstract fun getNetworkResponse (Ljava/lang/String;)Ljava/lang/String;
417+
public abstract fun incrementAnonymousTimesShown (Ljava/lang/String;)V
418+
public abstract fun isAnonymousDismissed (Ljava/lang/String;)Z
419+
public abstract fun isAnonymousInDelayPeriod (Ljava/lang/String;)Z
420+
public abstract fun isAnonymousMessagesExpired ()Z
421+
public abstract fun saveAnonymousMessages (Ljava/lang/String;J)V
366422
public abstract fun saveNetworkResponse (Ljava/lang/String;Ljava/lang/String;)V
423+
public abstract fun setAnonymousDismissed (Ljava/lang/String;Z)V
424+
public abstract fun setAnonymousNextShowTime (Ljava/lang/String;J)V
367425
}
368426

369427
public abstract interface class io/customer/messaginginapp/type/InAppEventListener {

messaginginapp/src/main/java/io/customer/messaginginapp/ModuleMessagingInApp.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ class ModuleMessagingInApp(
3939
gistProvider.setUserId(it.identifier)
4040
}
4141

42+
eventBus.subscribe<Event.AnonymousIdGeneratedEvent> {
43+
gistProvider.setAnonymousId(it.anonymousId)
44+
}
45+
4246
eventBus.subscribe<Event.ResetEvent> {
4347
logger.debug("Resetting user token")
4448
gistProvider.reset()

messaginginapp/src/main/java/io/customer/messaginginapp/di/DIGraphMessagingInApp.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package io.customer.messaginginapp.di
33
import com.google.gson.Gson
44
import io.customer.messaginginapp.MessagingInAppModuleConfig
55
import io.customer.messaginginapp.ModuleMessagingInApp
6+
import io.customer.messaginginapp.gist.data.AnonymousMessageManager
7+
import io.customer.messaginginapp.gist.data.AnonymousMessageManagerImpl
68
import io.customer.messaginginapp.gist.data.listeners.GistQueue
79
import io.customer.messaginginapp.gist.data.listeners.Queue
810
import io.customer.messaginginapp.gist.presentation.GistProvider
@@ -50,6 +52,11 @@ internal val SDKComponent.modalMessageParser: ModalMessageParser
5052
)
5153
}
5254

55+
internal val SDKComponent.anonymousMessageManager: AnonymousMessageManager
56+
get() = singleton<AnonymousMessageManager> {
57+
AnonymousMessageManagerImpl()
58+
}
59+
5360
/**
5461
* Get the [ModuleMessagingInApp] instance from the [CustomerIOInstance]
5562
* needed for the in-app messaging dismiss() method

0 commit comments

Comments
 (0)