diff --git a/DemoApp/DemoAppJava/app/src/main/java/co/optable/demoappjava/TheApplication.java b/DemoApp/DemoAppJava/app/src/main/java/co/optable/demoappjava/TheApplication.java index 37239ba..2f826a4 100644 --- a/DemoApp/DemoAppJava/app/src/main/java/co/optable/demoappjava/TheApplication.java +++ b/DemoApp/DemoAppJava/app/src/main/java/co/optable/demoappjava/TheApplication.java @@ -13,6 +13,7 @@ public void onCreate() { super.onCreate(); OptableConfig config = new OptableConfig(this, "prebidtest", "android-sdk"); + config.setOrigin("https://www.optable.co"); optable = new OptableSDK(config); } diff --git a/DemoApp/DemoAppKotlin/app/src/main/java/co/optable/androidsdkdemo/TheApplication.kt b/DemoApp/DemoAppKotlin/app/src/main/java/co/optable/androidsdkdemo/TheApplication.kt index 8467fb2..b7038e0 100644 --- a/DemoApp/DemoAppKotlin/app/src/main/java/co/optable/androidsdkdemo/TheApplication.kt +++ b/DemoApp/DemoAppKotlin/app/src/main/java/co/optable/androidsdkdemo/TheApplication.kt @@ -13,7 +13,13 @@ class TheApplication : Application() { override fun onCreate() { super.onCreate() - val config = OptableConfig(this, "prebidtest", "android-sdk", "ca.edge.optable.co") + val config = OptableConfig( + providedContext = this, + tenant = "prebidtest", + originSlug = "android-sdk", + host = "ca.edge.optable.co", + origin = "https://www.optable.co", + ) optable = OptableSDK(config) } diff --git a/README.md b/README.md index c2cb654..ee269cc 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,7 @@ developers running the DCN locally for testing. - `customUserAgent` An optional custom user agent string for network requests. - `skipAdvertisingIdDetection` Boolean flag to skip the detection of advertising IDs. Default is false. - `consents` Optional `OptableConsents` object for providing custom consent information. If not provided, default values will be used. +- `origin` An optional value for the HTTP `Origin` header sent with Optable API requests. Unrelated to `originSlug`. ```kotlin val config = OptableConfig( @@ -116,6 +117,7 @@ val config = OptableConfig( customUserAgent = "", skipAdvertisingIdDetection = false, consents = OptableConsents(), + origin = "https://www.acmeco.com", ) ``` @@ -135,7 +137,7 @@ val config = OptableConfig( ) ``` -Finally, an optional sixth boolean parameter `skipAdvertisingIdDetection` can be used to skip any ID info detection from +An optional boolean parameter `skipAdvertisingIdDetection` can be used to skip any ID info detection from `AdvertisingIdClient` which by default runs in a background co-routine. Disabling ad ID detection means that the SDK will not be able to automatically obtain the Google Advertising ID. For example, to disable ad ID detection, in Kotlin: @@ -146,6 +148,26 @@ val config = OptableConfig( ) ``` +Finally, an optional string parameter `origin` can be used to set the value of the HTTP `Origin` header sent with every +Optable API request. Mobile HTTP stacks do not send an `Origin` header by +default, and your DCN uses the request origin for several server-side features: some EID resolvers require it to be +present, and it also feeds site attribution and origin validation. Set it to the origin you want your mobile traffic +attributed to, for example: + +```kotlin +val config = OptableConfig( + ... + origin = "https://www.acmeco.com" +) +``` + +`origin` 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"); +``` + ### OptableIdentifiers There are many fields to identify the users. You can provide any user information using `OptableIdentifiers` class. diff --git a/android_sdk/src/main/java/co/optable/sdk/OptableConfig.kt b/android_sdk/src/main/java/co/optable/sdk/OptableConfig.kt index ba0ed4c..52dc92b 100644 --- a/android_sdk/src/main/java/co/optable/sdk/OptableConfig.kt +++ b/android_sdk/src/main/java/co/optable/sdk/OptableConfig.kt @@ -15,6 +15,8 @@ import android.content.Context * @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. + * @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. */ class OptableConfig @JvmOverloads constructor( providedContext: Context, @@ -27,6 +29,7 @@ class OptableConfig @JvmOverloads constructor( internal val customUserAgent: String? = null, internal val skipAdvertisingIdDetection: Boolean = false, var consents: OptableConsents = OptableConsents(), + var origin: String? = null, ) { internal val context = providedContext.applicationContext diff --git a/android_sdk/src/main/java/co/optable/sdk/core/network/RequestInterceptor.kt b/android_sdk/src/main/java/co/optable/sdk/core/network/RequestInterceptor.kt index af92c70..0ebf493 100644 --- a/android_sdk/src/main/java/co/optable/sdk/core/network/RequestInterceptor.kt +++ b/android_sdk/src/main/java/co/optable/sdk/core/network/RequestInterceptor.kt @@ -57,6 +57,11 @@ internal class RequestInterceptor( modifiedRequest.addHeader("Authorization", "Bearer $apiKey") } + val origin = config.origin + if (!origin.isNullOrBlank()) { + modifiedRequest.addHeader("Origin", origin) + } + val userAgent = userAgentHolder.getUserAgent() if (userAgent != null) { modifiedRequest.addHeader("User-Agent", userAgent) diff --git a/android_sdk/src/test/java/co/optable/sdk/OptableConfigTest.kt b/android_sdk/src/test/java/co/optable/sdk/OptableConfigTest.kt index c467883..c712136 100644 --- a/android_sdk/src/test/java/co/optable/sdk/OptableConfigTest.kt +++ b/android_sdk/src/test/java/co/optable/sdk/OptableConfigTest.kt @@ -39,6 +39,7 @@ class OptableConfigTest { assertNull(config.apiKey) assertNull(config.customUserAgent) assertFalse(config.skipAdvertisingIdDetection) + assertNull(config.origin) assertEquals(mockApplicationContext, config.context) } @@ -55,7 +56,8 @@ class OptableConfigTest { apiKey = "test-api-key", customUserAgent = "TestAgent/1.0", skipAdvertisingIdDetection = true, - consents = customConsents + consents = customConsents, + origin = "https://www.optable.co" ) assertEquals("custom-tenant", config.tenant) @@ -67,6 +69,7 @@ class OptableConfigTest { assertEquals("TestAgent/1.0", config.customUserAgent) assertTrue(config.skipAdvertisingIdDetection) assertEquals(customConsents, config.consents) + assertEquals("https://www.optable.co", config.origin) } @Test @@ -99,6 +102,21 @@ class OptableConfigTest { assertEquals(expectedUrl, config.getBaseUrl()) } + @Test + fun `origin property should be updatable`() { + val config = OptableConfig( + providedContext = mockContext, + tenant = "test-tenant", + originSlug = "test-slug" + ) + + assertNull(config.origin) + + config.origin = "https://www.acmeco.com" + + assertEquals("https://www.acmeco.com", config.origin) + } + @Test fun `consents property should be updatable`() { val config = OptableConfig( diff --git a/android_sdk/src/test/java/co/optable/sdk/core/MockWebServerTest.kt b/android_sdk/src/test/java/co/optable/sdk/core/MockWebServerTest.kt index e3b4df8..4cd3a3d 100644 --- a/android_sdk/src/test/java/co/optable/sdk/core/MockWebServerTest.kt +++ b/android_sdk/src/test/java/co/optable/sdk/core/MockWebServerTest.kt @@ -69,6 +69,7 @@ class MockWebServerTest { assertNull(request.headers["Authorization"]) assertNull(request.headers["X-Optable-Visitor"]) assertNull(request.headers["User-Agent"]) + assertNull(request.headers["Origin"]) assertEquals("application/json", request.headers["Accept"]) assertEquals( @@ -97,6 +98,42 @@ class MockWebServerTest { assertEquals("passport", request.headers["X-Optable-Visitor"]) } + @Test + fun `optional header, origin`() { + whenever(config.origin).thenReturn("https://www.optable.co") + + val request = makeRequest().request + + assertEquals("https://www.optable.co", request.headers["Origin"]) + } + + @Test + fun `no origin header when origin is blank`() { + whenever(config.origin).thenReturn(" ") + + val request = makeRequest().request + + assertNull(request.headers["Origin"]) + } + + @Test + fun `no origin header when origin is empty`() { + whenever(config.origin).thenReturn("") + + val request = makeRequest().request + + assertNull(request.headers["Origin"]) + } + + @Test + fun `no origin header when origin is null`() { + whenever(config.origin).thenReturn(null) + + val request = makeRequest().request + + assertNull(request.headers["Origin"]) + } + @Test fun `complete request`() { whenever(config.tenant).thenReturn("tenant") @@ -104,12 +141,15 @@ class MockWebServerTest { whenever(config.apiKey).thenReturn("apiKey") whenever(storage.getPassport()).thenReturn("passport") whenever(userAgentHolder.getUserAgent()).thenReturn("userAgent") + whenever(config.origin).thenReturn("https://www.optable.co") val request = makeRequest().request assertEquals("application/json", request.headers["Accept"]) assertEquals("Bearer apiKey", request.headers["Authorization"]) verify(config, times(1)).apiKey + assertEquals("https://www.optable.co", request.headers["Origin"]) + verify(config, times(1)).origin assertEquals("passport", request.headers["X-Optable-Visitor"]) verify(storage, times(1)).getPassport() assertEquals("userAgent", request.headers["User-Agent"])