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
17 changes: 17 additions & 0 deletions android-core/src/main/kotlin/com/mparticle/rokt/RoktSession.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.mparticle.rokt

/**
* A Rokt session suitable for handoff between native and non-native integrations.
*
* Includes the session id plus an optional short-lived session token used to authorize offers and
* events. Mirrors Web launcher options: `sessionId` is required for handoff; `sessionToken` is
* optional (Bearer continuity when present).
*
* @param sessionId The Rokt session identifier. Must be non-empty when passed to [com.mparticle.kits.Rokt.setSession].
* @param sessionToken Optional JWT session token used as a Bearer credential for offers and events.
* When omitted or blank, [com.mparticle.kits.Rokt.setSession] applies the session id only (same as
* Web `sessionId` without `sessionToken`).
* @param expiresAt Optional Unix epoch milliseconds when [sessionToken] expires (matches server
* `expires_at` when known). Ignored when [sessionToken] is absent.
*/
data class RoktSession @JvmOverloads constructor(val sessionId: String, val sessionToken: String? = null, val expiresAt: Long? = null)
37 changes: 37 additions & 0 deletions kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/Rokt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.graphics.Typeface
import com.mparticle.MParticle
import com.mparticle.internal.KitManager
import com.mparticle.internal.Logger
import com.mparticle.rokt.RoktSession
import com.rokt.roktsdk.PlacementOptions
import com.rokt.roktsdk.RoktConfig
import com.rokt.roktsdk.RoktEvent
Expand Down Expand Up @@ -137,16 +138,49 @@ class Rokt internal constructor(private val mKitManager: KitManager) {
}
}

/**
* Set the session to use for the next execute call.
*
* Use this when you have a session from a non-native integration (e.g. WebView)
* and want the session to stay consistent across integrations. Call before the next
* selectPlacements.
*
* Matches Web launcher options: pass [RoktSession.sessionId] with optional
* [RoktSession.sessionToken]. When the token is present, offers/events can send
* `Authorization: Bearer`. When only the id is present, the id is applied without Bearer
* seeding. Empty `sessionId` (or token without id) is ignored.
*
* @param session The session id and optional JWT session token (optional expiry).
*/
fun setSession(session: RoktSession) {
if (isEnabled()) {
resolveRoktKit()?.second?.setSession(session)
}
}

/**
* Get the current session (id + token) for use within a non-native integration e.g. WebView.
*
* @return The session, or null if disabled, no session is present, or the token has expired.
*/
fun getSession(): RoktSession? = if (isEnabled()) {
resolveRoktKit()?.second?.getSession()
} else {
null
}

/**
* Set the session id to use for the next execute call.
*
* This is useful for cases where you have a session id from a non-native integration,
* e.g. WebView, and you want the session to be consistent across integrations.
*
* **Note:** Empty strings are ignored and will not update the session.
* Prefer [setSession] so the session token is also applied for offers and events.
*
* @param sessionId The session id to be set. Must be a non-empty string.
*/
@Deprecated("Use setSession to set session id and session token.")
fun setSessionId(sessionId: String) {
if (isEnabled()) {
resolveRoktKit()?.second?.setSessionId(sessionId)
Expand All @@ -156,8 +190,11 @@ class Rokt internal constructor(private val mKitManager: KitManager) {
/**
* Get the session id to use within a non-native integration e.g. WebView.
*
* Prefer [getSession] to also read the session token.
*
* @return The session id or null if no session is present or SDK is not initialized.
*/
@Deprecated("Use getSession to read session id and session token.")
fun getSessionId(): String? = if (isEnabled()) {
resolveRoktKit()?.second?.getSessionId()
} else {
Expand Down
47 changes: 47 additions & 0 deletions kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.mparticle.internal.Logger
import com.mparticle.kits.KitIntegration.CommerceListener
import com.mparticle.kits.KitIntegration.IdentityListener
import com.mparticle.kits.KitIntegration.RoktListener
import com.mparticle.rokt.RoktSession
import com.rokt.roktsdk.PlacementOptions
import com.rokt.roktsdk.Rokt
import com.rokt.roktsdk.Rokt.SdkFrameworkType.Android
Expand All @@ -38,6 +39,7 @@ import kotlinx.coroutines.launch
import java.lang.ref.WeakReference
import java.math.BigDecimal
import java.net.URL
import com.rokt.roktsdk.RoktSession as NativeRoktSession

const val ROKT_ATTRIBUTE_SANDBOX_MODE: String = "sandbox"

Expand Down Expand Up @@ -330,22 +332,67 @@ class RoktKit :
Rokt.close()
}

/**
* Set the session to use for the next execute call.
*
* Matches Web launcher options: non-empty [RoktSession.sessionId] + [RoktSession.sessionToken]
* seeds Bearer continuity via [Rokt.setSession]; id-only falls back to [Rokt.setSessionId].
* Token without a non-empty id is ignored. Requires a Rokt Android SDK version that exposes
* [Rokt.setSession] / [NativeRoktSession].
*/
override fun setSession(session: RoktSession) {
val sessionId = session.sessionId.trim()
if (sessionId.isEmpty()) {
return
}
val sessionToken = session.sessionToken?.trim().orEmpty()
if (sessionToken.isNotEmpty()) {
Rokt.setSession(
NativeRoktSession(
sessionId = sessionId,
sessionToken = sessionToken,
expiresAt = session.expiresAt,
),
)
} else {
Rokt.setSessionId(sessionId)
}
}

/**
* Get the current session (id + token) for WebView / non-native handoff.
*/
override fun getSession(): RoktSession? {
val session = Rokt.getSession() ?: return null
return RoktSession(
sessionId = session.sessionId,
sessionToken = session.sessionToken,
expiresAt = session.expiresAt,
)
}

/**
* Set the session id to use for the next execute call.
* This is useful for cases where you have a session id from a non-native integration,
* e.g. WebView, and you want the session to be consistent across integrations.
*
* Prefer [setSession] so the session token is also applied for offers and events.
*
* @param sessionId The session id to be set. Must be a non-empty string.
*/
@Deprecated("Use setSession to set session id and session token.")
override fun setSessionId(sessionId: String) {
Rokt.setSessionId(sessionId)
}

/**
* Get the session id to use within a non-native integration e.g. WebView.
*
* Prefer [getSession] to also read the session token.
*
* @return The session id or null if no session is present.
*/
@Deprecated("Use getSession to read session id and session token.")
override fun getSessionId(): String? = Rokt.getSessionId()

override fun enrichAttributes(attributes: MutableMap<String, String>, user: FilteredMParticleUser?) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.mparticle.kits

import android.graphics.Typeface
import com.mparticle.rokt.RoktSession
import com.rokt.roktsdk.PlacementOptions
import com.rokt.roktsdk.RoktConfig
import com.rokt.roktsdk.RoktEvent
Expand Down Expand Up @@ -36,6 +37,10 @@ internal interface RoktKitBridge {

fun close()

fun setSession(session: RoktSession)

fun getSession(): RoktSession?

fun setSessionId(sessionId: String)

fun getSessionId(): String?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1488,6 +1488,61 @@ class RoktKitTests {
unmockkObject(Rokt)
}

@Test
fun testSetSession_delegatesToRoktSdk() {
mockkObject(Rokt)
every { Rokt.setSession(any()) } just runs

roktKit.setSession(com.mparticle.rokt.RoktSession("sid", "jwt", 123L))

verify {
Rokt.setSession(
match {
it.sessionId == "sid" && it.sessionToken == "jwt" && it.expiresAt == 123L
},
)
}
verify(exactly = 0) { Rokt.setSessionId(any()) }
unmockkObject(Rokt)
}

@Test
fun testSetSession_idOnlyFallsBackToSetSessionId() {
mockkObject(Rokt)
every { Rokt.setSessionId(any()) } just runs

roktKit.setSession(com.mparticle.rokt.RoktSession("sid"))

verify { Rokt.setSessionId("sid") }
verify(exactly = 0) { Rokt.setSession(any()) }
unmockkObject(Rokt)
}

@Test
fun testSetSession_blankIdIsIgnored() {
mockkObject(Rokt)

roktKit.setSession(com.mparticle.rokt.RoktSession(" ", "jwt"))

verify(exactly = 0) { Rokt.setSession(any()) }
verify(exactly = 0) { Rokt.setSessionId(any()) }
unmockkObject(Rokt)
}

@Test
fun testGetSession_mapsValueFromRoktSdk() {
mockkObject(Rokt)
every { Rokt.getSession() } returns com.rokt.roktsdk.RoktSession("sid", "jwt", 456L)

val result = roktKit.getSession()

assertEquals("sid", result?.sessionId)
assertEquals("jwt", result?.sessionToken)
assertEquals(456L, result?.expiresAt)
verify { Rokt.getSession() }
unmockkObject(Rokt)
}

@Test
fun testSetSessionId_delegatesToRoktSdk() {
mockkObject(Rokt)
Expand Down
34 changes: 34 additions & 0 deletions kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,40 @@ class RoktTest {
verify(roktListener, never()).selectShoppableAds(any(), any(), any(), any())
}

@Test
fun testSetSession_whenEnabled_delegatesToKitManager() {
configManager.enabled = true
val session = com.mparticle.rokt.RoktSession("sid", "jwt", 123L)
rokt.setSession(session)
verify(roktListener).setSession(session)
}

@Test
fun testSetSession_whenDisabled_doesNotCallKitManager() {
configManager.enabled = false
val session = com.mparticle.rokt.RoktSession("sid", "jwt", 123L)
rokt.setSession(session)
verify(roktListener, never()).setSession(any())
}

@Test
fun testGetSession_whenEnabled_delegatesToKitManager() {
configManager.enabled = true
val expected = com.mparticle.rokt.RoktSession("sid", "jwt", 123L)
`when`(roktListener.getSession()).thenReturn(expected)
val result = rokt.getSession()
verify(roktListener).getSession()
assertEquals(expected, result)
}

@Test
fun testGetSession_whenDisabled_returnsNull() {
configManager.enabled = false
val result = rokt.getSession()
verify(roktListener, never()).getSession()
assertNull(result)
}

@Test
fun testSetSessionId_whenEnabled_delegatesToKitManager() {
configManager.enabled = true
Expand Down
Loading