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
4 changes: 4 additions & 0 deletions Source/Core/EdgeAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ extension EdgeAPI {
headers[.userAgent] = userAgent
}

if let origin = config.origin, origin.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false {
headers[.origin] = origin
}

if let apiKey = config.apiKey {
headers[.authorization] = "Bearer \(apiKey)"
}
Expand Down
9 changes: 9 additions & 0 deletions Source/Public/OptableConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ public class OptableConfig: NSObject {
@objc
public var customUserAgent: String?

/// An optional value sent as the `Origin` HTTP header on every Optable API request,
/// identifying the origin you want your mobile traffic attributed to. E.g. `https://www.acmeco.com`.
/// When `nil` (the default), no `Origin` header is sent. Unrelated to `originSlug`.
@objc
public var origin: String?

/// Boolean flag to skip the detection of advertising IDs. Default is false.
@objc
public var skipAdvertisingIdDetection: Bool = false
Expand Down Expand Up @@ -103,6 +109,7 @@ public class OptableConfig: NSObject {
- insecure: Boolean flag that determines if insecure HTTP should be used instead of HTTPS. Default is false.
- apiKey: An optional API key for authentication. If the API Endpoint is enabled as private, a Service Account API key will be required.
- customUserAgent: An optional custom user agent string for network requests.
- origin: An optional value sent as the `Origin` HTTP header on every Optable API request, identifying the origin you want your mobile traffic attributed to. E.g. `https://www.acmeco.com`. No header is sent when nil. Unrelated to `originSlug`.
- skipAdvertisingIdDetection: Boolean flag to skip the detection of advertising IDs. Default is false.
*/
public init(
Expand All @@ -113,6 +120,7 @@ public class OptableConfig: NSObject {
insecure: Bool = false,
apiKey: String? = nil,
customUserAgent: String? = nil,
origin: String? = nil,
skipAdvertisingIdDetection: Bool = false
) {
self.tenant = tenant
Expand All @@ -122,6 +130,7 @@ public class OptableConfig: NSObject {
self.insecure = insecure
self.apiKey = apiKey
self.customUserAgent = customUserAgent
self.origin = origin
self.skipAdvertisingIdDetection = skipAdvertisingIdDetection
}
}
4 changes: 3 additions & 1 deletion Tests/Misc/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ enum T {
}

static let userAgent: String = "ios-integration-tests"


static let origin: String = "https://ios-integration-tests.optable.co"

static let apiKey: String = "test-api-key"
static let apiKeyBearer: String = "Bearer \(apiKey)"
}
Expand Down
87 changes: 87 additions & 0 deletions Tests/Unit/EdgeAPITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ class EdgeAPITests: XCTestCase {
)
lazy var sdk = OptableSDK(config: config)

lazy var originConfig = OptableConfig(
tenant: T.api.tenant.prebidtest,
originSlug: T.api.slug.iosSDK,
apiKey: T.api.apiKey,
customUserAgent: T.api.userAgent,
origin: T.api.origin,
)
lazy var originSDK = OptableSDK(config: originConfig)

// MARK: URL-s
/**
Expected output:
Expand Down Expand Up @@ -141,6 +150,84 @@ class EdgeAPITests: XCTestCase {

XCTAssertEqual(generatedHeaders["User-Agent"], T.api.userAgent)
XCTAssertEqual(generatedHeaders["Authorization"], T.api.apiKeyBearer)
XCTAssertNil(generatedHeaders["Origin"])
}

/**
When `origin` is configured, it is sent as the `Origin` header.
*/
func test_header_generation_with_origin() throws {
let generatedHeaders = originSDK.api.resolveHeaders().asDict

XCTAssertEqual(generatedHeaders["User-Agent"], T.api.userAgent)
XCTAssertEqual(generatedHeaders["Authorization"], T.api.apiKeyBearer)
XCTAssertEqual(generatedHeaders["Origin"], T.api.origin)
}

/**
`origin` is optional, and mutable after the config has been created.
*/
func test_header_generation_origin_is_optional() throws {
let config = OptableConfig(tenant: T.api.tenant.prebidtest, originSlug: T.api.slug.iosSDK)
let edgeAPI = EdgeAPI(config)

XCTAssertNil(config.origin)
XCTAssertNil(edgeAPI.resolveHeaders().asDict["Origin"])

config.origin = T.api.origin

XCTAssertEqual(edgeAPI.resolveHeaders().asDict["Origin"], T.api.origin)
}

/**
A blank `origin` (empty or whitespace-only) is suppressed rather than sent as an empty `Origin` header,
matching the Android SDK behavior.
*/
func test_header_generation_origin_is_not_blank() throws {
let config = OptableConfig(tenant: T.api.tenant.prebidtest, originSlug: T.api.slug.iosSDK)
let edgeAPI = EdgeAPI(config)

config.origin = ""
XCTAssertNil(edgeAPI.resolveHeaders().asDict["Origin"])

config.origin = " "
XCTAssertNil(edgeAPI.resolveHeaders().asDict["Origin"])

config.origin = T.api.origin
XCTAssertEqual(edgeAPI.resolveHeaders().asDict["Origin"], T.api.origin)
}

/**
The `Origin` header is unrelated to `originSlug`, which is sent as the `o` query parameter.
*/
func test_origin_does_not_affect_url_generation() throws {
let generatedURL = originSDK.api.buildEdgeAPIURL(endpoint: T.api.endpoint.identify)
let generatedURLComponents = URLComponents(url: generatedURL!, resolvingAgainstBaseURL: false)!

XCTAssertEqual(generatedURLComponents.queryItems!.first(where: { $0.name == "o" })!.value, T.api.slug.iosSDK)
XCTAssertNil(generatedURLComponents.queryItems?.first(where: { $0.name == "origin" }))
}

/**
Every endpoint carries the configured `Origin` header, and none of them carry one when it is unset.
*/
func test_origin_header_on_all_endpoints() throws {
typealias RequestFactory = (EdgeAPI) throws -> URLRequest?

let factories: [RequestFactory] = [
{ try $0.identify(ids: [.postalCode("1234567890")]) },
{ try $0.targeting(ids: [.emailAddress("12345")]) },
{ try $0.profile(traits: ["test-key": "test-value"]) },
{ try $0.witness(event: "test-event", properties: ["test-key": "test-value"]) },
]

try factories.forEach({ makeRequest in
let withoutOrigin = try makeRequest(sdk.api)
XCTAssertNil(withoutOrigin?.value(forHTTPHeaderField: "Origin"))

let withOrigin = try makeRequest(originSDK.api)
XCTAssertEqual(withOrigin?.value(forHTTPHeaderField: "Origin"), T.api.origin)
})
}

// MARK: URLRequest-s
Expand Down
3 changes: 2 additions & 1 deletion demo-ios-objc/demo-ios-objc/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(

OptableConfig *config = [[OptableConfig alloc] initWithTenant: @"prebidtest" originSlug: @"ios-sdk"];
config.host = @"na.cloud.optable.co";

config.origin = @"https://demo-ios-objc.optable.co";

OPTABLE = [[OptableSDK alloc] initWithConfig: config];
OPTABLE.delegate = delegate;

Expand Down
1 change: 1 addition & 0 deletions demo-ios-swift/demo-ios-swift/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
tenant: "prebidtest",
originSlug: "ios-sdk",
host: "ca.edge.optable.co",
origin: "https://demo-ios-swift.optable.co",
skipAdvertisingIdDetection: false
)
OPTABLE = OptableSDK(config: config)
Expand Down
6 changes: 6 additions & 0 deletions docs/usage-objc.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ You can call various SDK APIs on the instance as shown in the examples below. It

You can disable user agent `WKWebView` based auto-detection and provide your own value by setting the `useragent` parameter to a string value, similar to the Swift example.

By default the SDK does not send an `Origin` HTTP header. If your DCN expects one, you can set the optional `origin` parameter to the origin you want your mobile traffic attributed to, and its value will be sent as the `Origin` header on every Optable API request (`identify`, `targeting`, `profile`, `witness`):

```objective-c
config.origin = @"https://www.acmeco.com";
```

### Identify API

To associate a user device with an authenticated identifier such as an Email address, or with other known IDs such as the Apple ID for Advertising (IDFA), or even your own vendor or app level `PPID`, you can call the `identify` API as follows:
Expand Down
7 changes: 7 additions & 0 deletions docs/usage-swift.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ OPTABLE = OptableSDK(config: config)

The default value of `nil` for the `useragent` parameter enables the `WKWebView` auto-detection behavior.

By default the SDK does not send an `Origin` HTTP header. If your DCN expects one, you can set the optional `origin` parameter to the origin you want your mobile traffic attributed to, and its value will be sent as the `Origin` header on every Optable API request (`identify`, `targeting`, `profile`, `witness`):

```swift
let config = OptableConfig(..., origin: "https://www.acmeco.com")
OPTABLE = OptableSDK(config: config)
```

### Identify API

To associate a user device with an authenticated identifier such as an Email address, or with other known IDs such as the Apple ID for Advertising (IDFA), or even your own vendor or app level `PPID`, you can call the `identify` API as follows:
Expand Down
Loading