From 6ea330a46452b216dd1f92caff9072f74fcd58fe Mon Sep 17 00:00:00 2001 From: William Randolph Date: Tue, 20 Oct 2026 14:28:53 -0400 Subject: [PATCH 1/3] feat: send the mParticle session UUID to Branch Adds the current session UUID to the custom data of every event the Branch kit logs, under mp_session_uuid, so events Branch reports back can be matched to the original event. Skipped when there is no current session; a customer attribute with the same key is not overwritten. Co-Authored-By: Claude Opus 5.5 --- .../com/mparticle/kits/BranchMetricsKit.kt | 27 +++++++- .../com/mparticle/kits/BranchKitTests.kt | 63 +++++++++++++++++++ 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/kits/branch/branch-5/src/main/kotlin/com/mparticle/kits/BranchMetricsKit.kt b/kits/branch/branch-5/src/main/kotlin/com/mparticle/kits/BranchMetricsKit.kt index 9c863b57f..ebd6db1c9 100644 --- a/kits/branch/branch-5/src/main/kotlin/com/mparticle/kits/BranchMetricsKit.kt +++ b/kits/branch/branch-5/src/main/kotlin/com/mparticle/kits/BranchMetricsKit.kt @@ -120,7 +120,10 @@ class BranchMetricsKit : ): List = emptyList() override fun logEvent(event: MPEvent): List { - branchUtil?.createBranchEventFromMPEvent(event)?.logEvent(context) + branchUtil + ?.createBranchEventFromMPEvent(event) + ?.let { addSessionUuid(it, event.customAttributeStrings) } + ?.logEvent(context) val messages = LinkedList() messages.add(ReportingMessage.fromEvent(this, event)) return messages @@ -134,7 +137,10 @@ class BranchMetricsKit : ): List = emptyList() override fun logEvent(commerceEvent: CommerceEvent): List { - branchUtil?.createBranchEventFromMPCommerceEvent(commerceEvent)?.logEvent(context) + branchUtil + ?.createBranchEventFromMPCommerceEvent(commerceEvent) + ?.let { addSessionUuid(it, commerceEvent.customAttributeStrings) } + ?.logEvent(context) val messages: MutableList = LinkedList() messages.add(ReportingMessage.fromEvent(this, commerceEvent)) return messages @@ -151,6 +157,7 @@ class BranchMetricsKit : eventAttributes = HashMap() } branchUtil?.updateBranchEventWithCustomData(logScreenEvent, eventAttributes) + addSessionUuid(logScreenEvent, eventAttributes) logScreenEvent.logEvent(context) val messages = LinkedList() @@ -168,6 +175,21 @@ class BranchMetricsKit : } } + /** + * Adds the current mParticle session UUID to the event's custom data. A customer attribute with + * the same key wins, and nothing is added when there is no current session. + */ + internal fun addSessionUuid( + branchEvent: BranchEvent, + eventAttributes: Map?, + ): BranchEvent { + val sessionUuid = MParticle.getInstance()?.currentSession?.sessionUUID + if (!sessionUuid.isNullOrEmpty() && eventAttributes?.containsKey(SESSION_UUID_KEY) != true) { + branchEvent.addCustomDataProperty(SESSION_UUID_KEY, sessionUuid) + } + return branchEvent + } + private val branch: Branch? get() = settings[branchAppKey]?.let { Branch.getInstance() } @@ -318,6 +340,7 @@ class BranchMetricsKit : companion object { private const val FORWARD_SCREEN_VIEWS = "forwardScreenViews" private const val USER_IDENTIFICATION_TYPE = "userIdentificationType" + internal const val SESSION_UUID_KEY = "mp_session_uuid" const val NAME = "Branch Metrics" } } diff --git a/kits/branch/branch-5/src/test/kotlin/com/mparticle/kits/BranchKitTests.kt b/kits/branch/branch-5/src/test/kotlin/com/mparticle/kits/BranchKitTests.kt index dc92f808d..41c7a420b 100644 --- a/kits/branch/branch-5/src/test/kotlin/com/mparticle/kits/BranchKitTests.kt +++ b/kits/branch/branch-5/src/test/kotlin/com/mparticle/kits/BranchKitTests.kt @@ -1,7 +1,11 @@ package com.mparticle.kits import android.content.Context +import com.mparticle.MParticle import com.mparticle.MParticleOptions +import com.mparticle.Session +import io.branch.referral.util.BranchEvent +import org.junit.After import org.junit.Assert import org.junit.Test import org.mockito.Mockito @@ -50,4 +54,63 @@ class BranchKitTests { } Assert.fail("$className not found as a known integration.") } + + @After + fun tearDown() { + MParticle.setInstance(null) + } + + private fun mockCurrentSession(sessionUuid: String?) { + val session = Mockito.mock(Session::class.java) + Mockito.`when`(session.sessionUUID).thenReturn(sessionUuid) + val mParticle = Mockito.mock(MParticle::class.java) + Mockito.`when`(mParticle.currentSession).thenReturn(session) + MParticle.setInstance(mParticle) + } + + /** + * Verifies the current session UUID is added to the Branch event's custom data. + */ + @Test + fun testAddSessionUuidAddsCurrentSession() { + mockCurrentSession("11111111-2222-3333-4444-555555555555") + val branchEvent = Mockito.mock(BranchEvent::class.java) + + BranchMetricsKit().addSessionUuid(branchEvent, mapOf("color" to "blue")) + + Mockito + .verify(branchEvent) + .addCustomDataProperty(BranchMetricsKit.SESSION_UUID_KEY, "11111111-2222-3333-4444-555555555555") + } + + /** + * Verifies a customer attribute with the session UUID key is not overwritten. + */ + @Test + fun testAddSessionUuidKeepsCustomerValue() { + mockCurrentSession("11111111-2222-3333-4444-555555555555") + val branchEvent = Mockito.mock(BranchEvent::class.java) + + BranchMetricsKit().addSessionUuid( + branchEvent, + mapOf(BranchMetricsKit.SESSION_UUID_KEY to "customer-value"), + ) + + Mockito.verifyZeroInteractions(branchEvent) + } + + /** + * Verifies nothing is added when there is no current session. + */ + @Test + fun testAddSessionUuidSkipsWithoutSession() { + val mParticle = Mockito.mock(MParticle::class.java) + Mockito.`when`(mParticle.currentSession).thenReturn(null) + MParticle.setInstance(mParticle) + val branchEvent = Mockito.mock(BranchEvent::class.java) + + BranchMetricsKit().addSessionUuid(branchEvent, null) + + Mockito.verifyZeroInteractions(branchEvent) + } } From a80a7a03231542ff477c19f5135e9ca250727a50 Mon Sep 17 00:00:00 2001 From: William Randolph Date: Wed, 21 Oct 2026 11:44:59 -0400 Subject: [PATCH 2/3] feat: send the numeric session ID to Branch instead of the UUID The numeric session ID is the value mParticle exposes for the session in its data exports, so sending it lets Branch-reported events be matched without re-hashing the UUID. Co-Authored-By: Claude Opus 5.5 --- .../com/mparticle/kits/BranchMetricsKit.kt | 18 ++++---- .../com/mparticle/kits/BranchKitTests.kt | 41 ++++++++++++------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/kits/branch/branch-5/src/main/kotlin/com/mparticle/kits/BranchMetricsKit.kt b/kits/branch/branch-5/src/main/kotlin/com/mparticle/kits/BranchMetricsKit.kt index ebd6db1c9..e005297d7 100644 --- a/kits/branch/branch-5/src/main/kotlin/com/mparticle/kits/BranchMetricsKit.kt +++ b/kits/branch/branch-5/src/main/kotlin/com/mparticle/kits/BranchMetricsKit.kt @@ -122,7 +122,7 @@ class BranchMetricsKit : override fun logEvent(event: MPEvent): List { branchUtil ?.createBranchEventFromMPEvent(event) - ?.let { addSessionUuid(it, event.customAttributeStrings) } + ?.let { addSessionId(it, event.customAttributeStrings) } ?.logEvent(context) val messages = LinkedList() messages.add(ReportingMessage.fromEvent(this, event)) @@ -139,7 +139,7 @@ class BranchMetricsKit : override fun logEvent(commerceEvent: CommerceEvent): List { branchUtil ?.createBranchEventFromMPCommerceEvent(commerceEvent) - ?.let { addSessionUuid(it, commerceEvent.customAttributeStrings) } + ?.let { addSessionId(it, commerceEvent.customAttributeStrings) } ?.logEvent(context) val messages: MutableList = LinkedList() messages.add(ReportingMessage.fromEvent(this, commerceEvent)) @@ -157,7 +157,7 @@ class BranchMetricsKit : eventAttributes = HashMap() } branchUtil?.updateBranchEventWithCustomData(logScreenEvent, eventAttributes) - addSessionUuid(logScreenEvent, eventAttributes) + addSessionId(logScreenEvent, eventAttributes) logScreenEvent.logEvent(context) val messages = LinkedList() @@ -176,16 +176,16 @@ class BranchMetricsKit : } /** - * Adds the current mParticle session UUID to the event's custom data. A customer attribute with + * Adds the current mParticle session ID to the event's custom data. A customer attribute with * the same key wins, and nothing is added when there is no current session. */ - internal fun addSessionUuid( + internal fun addSessionId( branchEvent: BranchEvent, eventAttributes: Map?, ): BranchEvent { - val sessionUuid = MParticle.getInstance()?.currentSession?.sessionUUID - if (!sessionUuid.isNullOrEmpty() && eventAttributes?.containsKey(SESSION_UUID_KEY) != true) { - branchEvent.addCustomDataProperty(SESSION_UUID_KEY, sessionUuid) + val sessionId = MParticle.getInstance()?.currentSession?.sessionID + if (sessionId != null && sessionId != 0L && eventAttributes?.containsKey(SESSION_ID_KEY) != true) { + branchEvent.addCustomDataProperty(SESSION_ID_KEY, sessionId.toString()) } return branchEvent } @@ -340,7 +340,7 @@ class BranchMetricsKit : companion object { private const val FORWARD_SCREEN_VIEWS = "forwardScreenViews" private const val USER_IDENTIFICATION_TYPE = "userIdentificationType" - internal const val SESSION_UUID_KEY = "mp_session_uuid" + internal const val SESSION_ID_KEY = "mp_session_id" const val NAME = "Branch Metrics" } } diff --git a/kits/branch/branch-5/src/test/kotlin/com/mparticle/kits/BranchKitTests.kt b/kits/branch/branch-5/src/test/kotlin/com/mparticle/kits/BranchKitTests.kt index 41c7a420b..618b06625 100644 --- a/kits/branch/branch-5/src/test/kotlin/com/mparticle/kits/BranchKitTests.kt +++ b/kits/branch/branch-5/src/test/kotlin/com/mparticle/kits/BranchKitTests.kt @@ -60,40 +60,40 @@ class BranchKitTests { MParticle.setInstance(null) } - private fun mockCurrentSession(sessionUuid: String?) { + private fun mockCurrentSession(sessionId: Long) { val session = Mockito.mock(Session::class.java) - Mockito.`when`(session.sessionUUID).thenReturn(sessionUuid) + Mockito.`when`(session.sessionID).thenReturn(sessionId) val mParticle = Mockito.mock(MParticle::class.java) Mockito.`when`(mParticle.currentSession).thenReturn(session) MParticle.setInstance(mParticle) } /** - * Verifies the current session UUID is added to the Branch event's custom data. + * Verifies the current session ID is added to the Branch event's custom data. */ @Test - fun testAddSessionUuidAddsCurrentSession() { - mockCurrentSession("11111111-2222-3333-4444-555555555555") + fun testAddSessionIdAddsCurrentSession() { + mockCurrentSession(-7046029254386353131L) val branchEvent = Mockito.mock(BranchEvent::class.java) - BranchMetricsKit().addSessionUuid(branchEvent, mapOf("color" to "blue")) + BranchMetricsKit().addSessionId(branchEvent, mapOf("color" to "blue")) Mockito .verify(branchEvent) - .addCustomDataProperty(BranchMetricsKit.SESSION_UUID_KEY, "11111111-2222-3333-4444-555555555555") + .addCustomDataProperty(BranchMetricsKit.SESSION_ID_KEY, "-7046029254386353131") } /** - * Verifies a customer attribute with the session UUID key is not overwritten. + * Verifies a customer attribute with the session ID key is not overwritten. */ @Test - fun testAddSessionUuidKeepsCustomerValue() { - mockCurrentSession("11111111-2222-3333-4444-555555555555") + fun testAddSessionIdKeepsCustomerValue() { + mockCurrentSession(-7046029254386353131L) val branchEvent = Mockito.mock(BranchEvent::class.java) - BranchMetricsKit().addSessionUuid( + BranchMetricsKit().addSessionId( branchEvent, - mapOf(BranchMetricsKit.SESSION_UUID_KEY to "customer-value"), + mapOf(BranchMetricsKit.SESSION_ID_KEY to "customer-value"), ) Mockito.verifyZeroInteractions(branchEvent) @@ -103,13 +103,26 @@ class BranchKitTests { * Verifies nothing is added when there is no current session. */ @Test - fun testAddSessionUuidSkipsWithoutSession() { + fun testAddSessionIdSkipsWithoutSession() { val mParticle = Mockito.mock(MParticle::class.java) Mockito.`when`(mParticle.currentSession).thenReturn(null) MParticle.setInstance(mParticle) val branchEvent = Mockito.mock(BranchEvent::class.java) - BranchMetricsKit().addSessionUuid(branchEvent, null) + BranchMetricsKit().addSessionId(branchEvent, null) + + Mockito.verifyZeroInteractions(branchEvent) + } + + /** + * Verifies nothing is added when the session has no UUID, which the SDK reports as an ID of 0. + */ + @Test + fun testAddSessionIdSkipsZeroId() { + mockCurrentSession(0L) + val branchEvent = Mockito.mock(BranchEvent::class.java) + + BranchMetricsKit().addSessionId(branchEvent, null) Mockito.verifyZeroInteractions(branchEvent) } From e4dd24cf3348e474e92c135a35590597fca9bdc4 Mon Sep 17 00:00:00 2001 From: William Randolph Date: Wed, 21 Oct 2026 15:43:37 -0400 Subject: [PATCH 3/3] feat: send the session ID to Branch only when sendSessionId is enabled Reads the sendSessionId connection setting, off by default, and adds the ID through a BranchEvent.withSessionId extension. Co-Authored-By: Claude Opus 5.5 --- .../com/mparticle/kits/BranchMetricsKit.kt | 40 +++++++++------- .../com/mparticle/kits/BranchKitTests.kt | 48 ++++++------------- 2 files changed, 36 insertions(+), 52 deletions(-) diff --git a/kits/branch/branch-5/src/main/kotlin/com/mparticle/kits/BranchMetricsKit.kt b/kits/branch/branch-5/src/main/kotlin/com/mparticle/kits/BranchMetricsKit.kt index e005297d7..e5dc61802 100644 --- a/kits/branch/branch-5/src/main/kotlin/com/mparticle/kits/BranchMetricsKit.kt +++ b/kits/branch/branch-5/src/main/kotlin/com/mparticle/kits/BranchMetricsKit.kt @@ -49,6 +49,7 @@ class BranchMetricsKit : private var isMpidIdentityType = false var identityType: IdentityType? = IdentityType.CustomerId private var mSendScreenEvents = false + private var sendSessionId = false private var branchUtil: BranchUtil? = null override fun getInstance(): Branch? = branch @@ -74,6 +75,7 @@ class BranchMetricsKit : mSendScreenEvents = sendScreenEvents != null && sendScreenEvents.equals("true", ignoreCase = true) + sendSessionId = settings[SEND_SESSION_ID].equals("true", ignoreCase = true) setIdentityType(settings) return emptyList() } @@ -122,7 +124,7 @@ class BranchMetricsKit : override fun logEvent(event: MPEvent): List { branchUtil ?.createBranchEventFromMPEvent(event) - ?.let { addSessionId(it, event.customAttributeStrings) } + ?.withSessionId(currentSessionId, event.customAttributeStrings) ?.logEvent(context) val messages = LinkedList() messages.add(ReportingMessage.fromEvent(this, event)) @@ -139,7 +141,7 @@ class BranchMetricsKit : override fun logEvent(commerceEvent: CommerceEvent): List { branchUtil ?.createBranchEventFromMPCommerceEvent(commerceEvent) - ?.let { addSessionId(it, commerceEvent.customAttributeStrings) } + ?.withSessionId(currentSessionId, commerceEvent.customAttributeStrings) ?.logEvent(context) val messages: MutableList = LinkedList() messages.add(ReportingMessage.fromEvent(this, commerceEvent)) @@ -157,8 +159,7 @@ class BranchMetricsKit : eventAttributes = HashMap() } branchUtil?.updateBranchEventWithCustomData(logScreenEvent, eventAttributes) - addSessionId(logScreenEvent, eventAttributes) - logScreenEvent.logEvent(context) + logScreenEvent.withSessionId(currentSessionId, eventAttributes).logEvent(context) val messages = LinkedList() messages.add( @@ -175,20 +176,8 @@ class BranchMetricsKit : } } - /** - * Adds the current mParticle session ID to the event's custom data. A customer attribute with - * the same key wins, and nothing is added when there is no current session. - */ - internal fun addSessionId( - branchEvent: BranchEvent, - eventAttributes: Map?, - ): BranchEvent { - val sessionId = MParticle.getInstance()?.currentSession?.sessionID - if (sessionId != null && sessionId != 0L && eventAttributes?.containsKey(SESSION_ID_KEY) != true) { - branchEvent.addCustomDataProperty(SESSION_ID_KEY, sessionId.toString()) - } - return branchEvent - } + private val currentSessionId: Long? + get() = if (sendSessionId) MParticle.getInstance()?.currentSession?.sessionID else null private val branch: Branch? get() = settings[branchAppKey]?.let { Branch.getInstance() } @@ -337,9 +326,24 @@ class BranchMetricsKit : override fun onApplicationBackground() {} + /** + * Adds the mParticle session ID to the event's custom data. A customer attribute with the same + * key wins, and nothing is added without a session ID. + */ + internal fun BranchEvent.withSessionId( + sessionId: Long?, + attributes: Map?, + ): BranchEvent = + apply { + if (sessionId != null && sessionId != 0L && attributes?.containsKey(SESSION_ID_KEY) != true) { + addCustomDataProperty(SESSION_ID_KEY, sessionId.toString()) + } + } + companion object { private const val FORWARD_SCREEN_VIEWS = "forwardScreenViews" private const val USER_IDENTIFICATION_TYPE = "userIdentificationType" + private const val SEND_SESSION_ID = "sendSessionId" internal const val SESSION_ID_KEY = "mp_session_id" const val NAME = "Branch Metrics" } diff --git a/kits/branch/branch-5/src/test/kotlin/com/mparticle/kits/BranchKitTests.kt b/kits/branch/branch-5/src/test/kotlin/com/mparticle/kits/BranchKitTests.kt index 618b06625..68ccf4c9f 100644 --- a/kits/branch/branch-5/src/test/kotlin/com/mparticle/kits/BranchKitTests.kt +++ b/kits/branch/branch-5/src/test/kotlin/com/mparticle/kits/BranchKitTests.kt @@ -1,11 +1,8 @@ package com.mparticle.kits import android.content.Context -import com.mparticle.MParticle import com.mparticle.MParticleOptions -import com.mparticle.Session import io.branch.referral.util.BranchEvent -import org.junit.After import org.junit.Assert import org.junit.Test import org.mockito.Mockito @@ -55,28 +52,14 @@ class BranchKitTests { Assert.fail("$className not found as a known integration.") } - @After - fun tearDown() { - MParticle.setInstance(null) - } - - private fun mockCurrentSession(sessionId: Long) { - val session = Mockito.mock(Session::class.java) - Mockito.`when`(session.sessionID).thenReturn(sessionId) - val mParticle = Mockito.mock(MParticle::class.java) - Mockito.`when`(mParticle.currentSession).thenReturn(session) - MParticle.setInstance(mParticle) - } - /** - * Verifies the current session ID is added to the Branch event's custom data. + * Verifies the session ID is added to the Branch event's custom data. */ @Test - fun testAddSessionIdAddsCurrentSession() { - mockCurrentSession(-7046029254386353131L) + fun testWithSessionIdAddsSessionId() { val branchEvent = Mockito.mock(BranchEvent::class.java) - BranchMetricsKit().addSessionId(branchEvent, mapOf("color" to "blue")) + with(BranchMetricsKit()) { branchEvent.withSessionId(-7046029254386353131L, mapOf("color" to "blue")) } Mockito .verify(branchEvent) @@ -87,14 +70,15 @@ class BranchKitTests { * Verifies a customer attribute with the session ID key is not overwritten. */ @Test - fun testAddSessionIdKeepsCustomerValue() { - mockCurrentSession(-7046029254386353131L) + fun testWithSessionIdKeepsCustomerValue() { val branchEvent = Mockito.mock(BranchEvent::class.java) - BranchMetricsKit().addSessionId( - branchEvent, - mapOf(BranchMetricsKit.SESSION_ID_KEY to "customer-value"), - ) + with(BranchMetricsKit()) { + branchEvent.withSessionId( + -7046029254386353131L, + mapOf(BranchMetricsKit.SESSION_ID_KEY to "customer-value"), + ) + } Mockito.verifyZeroInteractions(branchEvent) } @@ -103,13 +87,10 @@ class BranchKitTests { * Verifies nothing is added when there is no current session. */ @Test - fun testAddSessionIdSkipsWithoutSession() { - val mParticle = Mockito.mock(MParticle::class.java) - Mockito.`when`(mParticle.currentSession).thenReturn(null) - MParticle.setInstance(mParticle) + fun testWithSessionIdSkipsWithoutSession() { val branchEvent = Mockito.mock(BranchEvent::class.java) - BranchMetricsKit().addSessionId(branchEvent, null) + with(BranchMetricsKit()) { branchEvent.withSessionId(null, null) } Mockito.verifyZeroInteractions(branchEvent) } @@ -118,11 +99,10 @@ class BranchKitTests { * Verifies nothing is added when the session has no UUID, which the SDK reports as an ID of 0. */ @Test - fun testAddSessionIdSkipsZeroId() { - mockCurrentSession(0L) + fun testWithSessionIdSkipsZeroId() { val branchEvent = Mockito.mock(BranchEvent::class.java) - BranchMetricsKit().addSessionId(branchEvent, null) + with(BranchMetricsKit()) { branchEvent.withSessionId(0L, null) } Mockito.verifyZeroInteractions(branchEvent) }