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
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,13 +200,20 @@ val config = OptableConfig(
)
```

`origin` can also be set after the config has been created, which is the convenient form from Java:
`origin`, and every other optional parameter, can also be set after the config has been created, which is the
convenient form from Java:

```java
OptableConfig config = new OptableConfig(this, "prebidtest", "android-sdk");
config.setOrigin("https://www.acmeco.com");
config.setApiKey(API_KEY);
config.setCacheTtl(60 * 60L);
optable = new OptableSDK(config);
```

`host`, `path`, `insecure` and `consents` are read when the `OptableSDK` instance is created, so set them before
creating it (use `setConsents()` to change consents afterwards). The other parameters apply from the next call.

### OptableIdentifier

There are many fields to identify the users. You can provide any user information using the `OptableIdentifier` class.
Expand Down
22 changes: 14 additions & 8 deletions android_sdk/src/main/java/co/optable/sdk/OptableConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,39 @@ import android.content.Context
/**
* Configuration class for Optable integration.
*
* The optional settings can also be set after construction, which is how Java callers set them without passing
* every preceding parameter. Settings marked "read when `OptableSDK` is created" must be set before the config is
* passed to `OptableSDK`; changing them later has no effect on that instance. The others apply from the next call.
*
* @param tenant The tenant name associated with the configuration. E.g. `acmeco.optable.co` => `acmeco`.
* @param originSlug The DCN's Source Slug. E.g. `acmeco-sdk`.
* @param host The hostname of the Optable endpoint. Default value is "na.edge.optable.co".
* @param path The API path to be appended to the host. Default value is "v2".
* Read when `OptableSDK` is created.
* @param path The API path to be appended to the host. Default value is "v2". Read when `OptableSDK` is created.
* @param insecure Boolean flag that determines if insecure HTTP should be used instead of HTTPS. Default is false.
* Read when `OptableSDK` is created.
* @param apiKey An optional API key for authentication. If the API Endpoint is enabled as private, a Service Account API key will be required.
* @param customUserAgent An optional custom user agent string for network requests.
* @param skipAdvertisingIdDetection Boolean flag to skip the detection of advertising IDs. Default is false.
* @param consents Optional `OptableConsents` object for providing custom consent information. If not provided, default values will be used.
* Read when `OptableSDK` is created; use `OptableSDK.setConsents` to change them afterwards.
* @param origin An optional value for the HTTP `Origin` header sent with Optable API requests. Unrelated to `originSlug`.
* Settable after construction, which is how Java callers set it without passing every preceding parameter.
* @param cacheTtl The targeting cache time to live in seconds. Default value is 24 hours.
* A value of zero or less disables the cache.
*/
class OptableConfig @JvmOverloads constructor(
providedContext: Context,
internal val tenant: String,
internal val originSlug: String,
internal val host: String = "na.edge.optable.co",
internal val path: String = "v2",
internal val insecure: Boolean = false,
internal val apiKey: String? = null,
internal val customUserAgent: String? = null,
var host: String = "na.edge.optable.co",
var path: String = "v2",
var insecure: Boolean = false,
@Volatile var apiKey: String? = null,
@Volatile var customUserAgent: String? = null,
internal val skipAdvertisingIdDetection: Boolean = false,
var consents: OptableConsents = OptableConsents(),
var origin: String? = null,
internal val cacheTtl: Long = DEFAULT_TARGETING_CACHE_TTL_SECONDS,
@Volatile var cacheTtl: Long = DEFAULT_TARGETING_CACHE_TTL_SECONDS,
) {

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import org.json.JSONObject
* Manages SharedPreferences for Optable SDK.
*/
internal class LocalStorage(
config: OptableConfig,
private val config: OptableConfig,
) {

companion object {
Expand All @@ -31,7 +31,7 @@ internal class LocalStorage(
private val passportKey = generateUniqueKey("PASS", config)
private val targetingKey = generateUniqueKey("TGT", config)
private val targetingTimestampKey = generateUniqueKey("TGT_TS", config)
private val targetingCacheTtlMs = config.cacheTtl.coerceAtMost(Long.MAX_VALUE / 1000) * 1000L
private val targetingCacheTtlMs get() = config.cacheTtl.coerceAtMost(Long.MAX_VALUE / 1000) * 1000L
private val id5SignatureKey = generateUniqueKey("ID5SIG", config)

fun getPassport(): String? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import android.webkit.WebSettings
import co.optable.sdk.OptableConfig

internal class UserAgentHolder(
config: OptableConfig,
private val config: OptableConfig,
) {

private val cachedUserAgent: String? = config.customUserAgent ?: defaultUserAgent(config.context)
private val cachedDefaultUserAgent: String? by lazy { defaultUserAgent(config.context) }

fun getUserAgent() = cachedUserAgent
fun getUserAgent() = config.customUserAgent ?: cachedDefaultUserAgent


private fun defaultUserAgent(context: Context): String? {
Expand Down
20 changes: 20 additions & 0 deletions android_sdk/src/test/java/co/optable/sdk/OptableConfigTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,26 @@ class OptableConfigTest {
assertEquals(60L, config.cacheTtl)
}

@Test
fun `optional properties should be settable after construction`() {
val config = OptableConfig(mockContext, "test-tenant", "test-slug")

config.host = "custom.host.com"
config.path = "v3"
config.insecure = true
config.apiKey = "test-api-key"
config.customUserAgent = "TestAgent/1.0"
config.cacheTtl = 60L

assertEquals("custom.host.com", config.host)
assertEquals("v3", config.path)
assertTrue(config.insecure)
assertEquals("test-api-key", config.apiKey)
assertEquals("TestAgent/1.0", config.customUserAgent)
assertEquals(60L, config.cacheTtl)
assertEquals("http://custom.host.com/v3/", config.getBaseUrl())
}

@Test
fun `getBaseUrl should return HTTPS url when insecure is false`() {
val config = OptableConfig(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,20 @@ class LocalStorageTest {
assertNull(storage.getTargeting())
}

@Test
fun `getTargeting should apply a TTL changed after construction`() {
val config = OptableConfig(mockContext, "testhost", "testtenant", "testslug")
val storage = LocalStorage(config)
val targetingKey = getPrivateKey(storage, "targetingKey")
every { mockSharedPreferences.getString(targetingKey, null) } returns cachedTargetingJson(storage)
cacheTimestamp(storage, System.currentTimeMillis() - 3 * HOUR_MS)
assertEquals(targeting.gamTargetingKeywords, storage.getTargeting()?.gamTargetingKeywords)

config.cacheTtl = 2 * 60 * 60L

assertNull(storage.getTargeting())
}

@Test
fun `getTargeting should keep the cache valid when the configured TTL is huge`() {
val storage = LocalStorage(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package co.optable.sdk.core

import android.content.Context
import android.webkit.WebSettings
import co.optable.sdk.OptableConfig
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkAll
import io.mockk.verify
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test

class UserAgentHolderTest {

private lateinit var context: Context
private lateinit var config: OptableConfig

@Before
fun setup() {
context = mockk()
config = OptableConfig(mockk { every { applicationContext } returns context }, "tenant", "slug")
mockkStatic(WebSettings::class)
every { WebSettings.getDefaultUserAgent(context) } returns "DefaultAgent/1.0"
}

@After
fun teardown() {
unmockkAll()
}

@Test
fun `getUserAgent should return the default user agent when no custom one is set`() {
assertEquals("DefaultAgent/1.0", UserAgentHolder(config).getUserAgent())
}

@Test
fun `getUserAgent should apply a custom user agent changed after construction`() {
val holder = UserAgentHolder(config)

config.customUserAgent = "CustomAgent/1.0"
assertEquals("CustomAgent/1.0", holder.getUserAgent())

config.customUserAgent = null
assertEquals("DefaultAgent/1.0", holder.getUserAgent())
}

@Test
fun `getUserAgent should not read the default user agent when a custom one is set`() {
config.customUserAgent = "CustomAgent/1.0"

assertEquals("CustomAgent/1.0", UserAgentHolder(config).getUserAgent())
verify(exactly = 0) { WebSettings.getDefaultUserAgent(any()) }
}
}
Loading