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
15 changes: 12 additions & 3 deletions Source/Misc/IABConsent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,22 @@ enum IABConsent {
}

static var gppTC: String? {
UserDefaults.standard.string(forKey: Keys.IABGPP_HDR_GppString)
?? UserDefaults.standard.string(forKey: Keys.IABGPP_2_TCString)
nonBlankString(forKey: Keys.IABGPP_HDR_GppString)
?? nonBlankString(forKey: Keys.IABGPP_2_TCString)
}

/// The CMP stores section IDs separated by underscores ("2_7"); the Edge API takes them comma-separated ("2,7").
/// Only read when `IABGPP_HDR_GppString` is set: the section IDs describe the CMP's own string and must never be sent with `IABGPP_2_TCString`.
static var gppSid: String? {
UserDefaults.standard.string(forKey: Keys.IABGPP_GppSID)?
guard nonBlankString(forKey: Keys.IABGPP_HDR_GppString) != nil else { return nil }
return nonBlankString(forKey: Keys.IABGPP_GppSID)?
.replacingOccurrences(of: "_", with: ",")
}

/// A blank value (empty or whitespace only) counts as unset.
private static func nonBlankString(forKey key: String) -> String? {
guard let value = UserDefaults.standard.string(forKey: key),
!value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return nil }
return value
}
}
2 changes: 1 addition & 1 deletion Source/Public/OptableConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ public final class OptableConfig: NSObject, Sendable {
/**
A comma-separated list of up to two sections applicable in a given GPP privacy string. This value is required when gpp is present.

> If not set and `gpp` is read from UserDefaults, SDK will also read `IABGPP_GppSID` from UserDefaults, as stated in [](https://github.com/InteractiveAdvertisingBureau/Global-Privacy-Platform/blob/main/Core/CMP%20API%20Specification.md)
> If not set and `gpp` is read from UserDefaults `IABGPP_HDR_GppString`, SDK will also read `IABGPP_GppSID` from UserDefaults, as stated in [](https://github.com/InteractiveAdvertisingBureau/Global-Privacy-Platform/blob/main/Core/CMP%20API%20Specification.md)
*/
@objc
public var gppSid: String? {
Expand Down
63 changes: 63 additions & 0 deletions Tests/Unit/EdgeAPITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,69 @@ class EdgeAPITests: XCTestCase {
UserDefaults.standard.set(nil, forKey: IABConsent.Keys.IABGPP_2_TCString)
}

func test_url_generation_privacy_regulations_global_legacyGPPKey_ignoresGlobalGPPSid() throws {
UserDefaults.standard.set(nil, forKey: IABConsent.Keys.IABGPP_HDR_GppString)
UserDefaults.standard.set("2_7", forKey: IABConsent.Keys.IABGPP_GppSID)
UserDefaults.standard.set("legacyGPP", forKey: IABConsent.Keys.IABGPP_2_TCString)

let config = OptableConfig(tenant: T.api.tenant.prebidtest, originSlug: T.api.slug.iosSDK)
let generatedURL = OptableSDK(config: config).api.buildEdgeAPIURL(endpoint: T.api.endpoint.identify)
let generatedURLComponents = URLComponents(url: generatedURL!, resolvingAgainstBaseURL: false)!

XCTAssertEqual(generatedURLComponents.queryItems?.first(where: { $0.name == "gpp" })?.value, "legacyGPP")
XCTAssertNil(generatedURLComponents.queryItems?.first(where: { $0.name == "gpp_sid" }))

UserDefaults.standard.set(nil, forKey: IABConsent.Keys.IABGPP_GppSID)
UserDefaults.standard.set(nil, forKey: IABConsent.Keys.IABGPP_2_TCString)
}

func test_url_generation_privacy_regulations_global_blankGPP_fallsBackToLegacyGPPKey() throws {
UserDefaults.standard.set("", forKey: IABConsent.Keys.IABGPP_HDR_GppString)
UserDefaults.standard.set(nil, forKey: IABConsent.Keys.IABGPP_GppSID)
UserDefaults.standard.set("legacyGPP", forKey: IABConsent.Keys.IABGPP_2_TCString)

let config = OptableConfig(tenant: T.api.tenant.prebidtest, originSlug: T.api.slug.iosSDK)
let generatedURL = OptableSDK(config: config).api.buildEdgeAPIURL(endpoint: T.api.endpoint.identify)
let generatedURLComponents = URLComponents(url: generatedURL!, resolvingAgainstBaseURL: false)!

XCTAssertEqual(generatedURLComponents.queryItems?.first(where: { $0.name == "gpp" })?.value, "legacyGPP")

UserDefaults.standard.set(nil, forKey: IABConsent.Keys.IABGPP_HDR_GppString)
UserDefaults.standard.set(nil, forKey: IABConsent.Keys.IABGPP_2_TCString)
}

func test_url_generation_privacy_regulations_global_blankGPPSid() throws {
UserDefaults.standard.set("globalGPP", forKey: IABConsent.Keys.IABGPP_HDR_GppString)
UserDefaults.standard.set("", forKey: IABConsent.Keys.IABGPP_GppSID)
UserDefaults.standard.set(nil, forKey: IABConsent.Keys.IABGPP_2_TCString)

let config = OptableConfig(tenant: T.api.tenant.prebidtest, originSlug: T.api.slug.iosSDK)
let generatedURL = OptableSDK(config: config).api.buildEdgeAPIURL(endpoint: T.api.endpoint.identify)
let generatedURLComponents = URLComponents(url: generatedURL!, resolvingAgainstBaseURL: false)!

XCTAssertEqual(generatedURLComponents.queryItems?.first(where: { $0.name == "gpp" })?.value, "globalGPP")
XCTAssertNil(generatedURLComponents.queryItems?.first(where: { $0.name == "gpp_sid" }))

UserDefaults.standard.set(nil, forKey: IABConsent.Keys.IABGPP_HDR_GppString)
UserDefaults.standard.set(nil, forKey: IABConsent.Keys.IABGPP_GppSID)
}

func test_url_generation_privacy_regulations_global_blankGPPKeys() throws {
UserDefaults.standard.set("", forKey: IABConsent.Keys.IABGPP_HDR_GppString)
UserDefaults.standard.set(nil, forKey: IABConsent.Keys.IABGPP_GppSID)
UserDefaults.standard.set(" ", forKey: IABConsent.Keys.IABGPP_2_TCString)

let config = OptableConfig(tenant: T.api.tenant.prebidtest, originSlug: T.api.slug.iosSDK)
let generatedURL = OptableSDK(config: config).api.buildEdgeAPIURL(endpoint: T.api.endpoint.identify)
let generatedURLComponents = URLComponents(url: generatedURL!, resolvingAgainstBaseURL: false)!

XCTAssertNil(generatedURLComponents.queryItems?.first(where: { $0.name == "gpp" }))
XCTAssertNil(generatedURLComponents.queryItems?.first(where: { $0.name == "gpp_sid" }))

UserDefaults.standard.set(nil, forKey: IABConsent.Keys.IABGPP_HDR_GppString)
UserDefaults.standard.set(nil, forKey: IABConsent.Keys.IABGPP_2_TCString)
}

func test_url_generation_privacy_regulations_explicitGPP_ignoresGlobalGPPSid() throws {
UserDefaults.standard.set("globalGPP", forKey: IABConsent.Keys.IABGPP_HDR_GppString)
UserDefaults.standard.set("2_7", forKey: IABConsent.Keys.IABGPP_GppSID)
Expand Down
4 changes: 2 additions & 2 deletions docs/usage-objc.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,9 @@ If you don't set them, the SDK reads the values that an IAB-compliant consent ma
| `gppSid` | `IABGPP_GppSID` | `gpp_sid` |
| `reg` | none | `reg` |

`gdpr` and `gdprConsent` are used together: if either one is missing from the config, the SDK ignores both and uses the `NSUserDefaults` values instead. `gppSid` falls back to `IABGPP_GppSID` only when `gpp` also comes from `NSUserDefaults`, so the section IDs always match the string they describe: if you set `gpp` in the config, set `gppSid` too. The CMP stores section IDs separated by underscores (`2_7`); the SDK sends them comma-separated (`2,7`).
`gdpr` and `gdprConsent` are used together: if either one is missing from the config, the SDK ignores both and uses the `NSUserDefaults` values instead. `gppSid` falls back to `IABGPP_GppSID` only when `gpp` comes from `NSUserDefaults` `IABGPP_HDR_GppString` (not the legacy `IABGPP_2_TCString`), so the section IDs always match the string they describe: if you set `gpp` in the config, set `gppSid` too. The CMP stores section IDs separated by underscores (`2_7`); the SDK sends them comma-separated (`2,7`).

If `IABGPP_HDR_GppString` is not set, the SDK also reads `gpp` from `IABGPP_2_TCString`, a key that earlier versions of the SDK used.
If `IABGPP_HDR_GppString` is not set, the SDK also reads `gpp` from `IABGPP_2_TCString`, a key that earlier versions of the SDK used. Empty or whitespace-only values count as not set.

### Identify API

Expand Down
4 changes: 2 additions & 2 deletions docs/usage-swift.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ If you don't set them, the SDK reads the values that an IAB-compliant consent ma
| `gppSid` | `IABGPP_GppSID` | `gpp_sid` |
| `reg` | none | `reg` |

`gdpr` and `gdprConsent` are used together: if either one is missing from the config, the SDK ignores both and uses the `UserDefaults` values instead. `gppSid` falls back to `IABGPP_GppSID` only when `gpp` also comes from `UserDefaults`, so the section IDs always match the string they describe: if you set `gpp` in the config, set `gppSid` too. The CMP stores section IDs separated by underscores (`2_7`); the SDK sends them comma-separated (`2,7`).
`gdpr` and `gdprConsent` are used together: if either one is missing from the config, the SDK ignores both and uses the `UserDefaults` values instead. `gppSid` falls back to `IABGPP_GppSID` only when `gpp` comes from `UserDefaults` `IABGPP_HDR_GppString` (not the legacy `IABGPP_2_TCString`), so the section IDs always match the string they describe: if you set `gpp` in the config, set `gppSid` too. The CMP stores section IDs separated by underscores (`2_7`); the SDK sends them comma-separated (`2,7`).

If `IABGPP_HDR_GppString` is not set, the SDK also reads `gpp` from `IABGPP_2_TCString`, a key that earlier versions of the SDK used.
If `IABGPP_HDR_GppString` is not set, the SDK also reads `gpp` from `IABGPP_2_TCString`, a key that earlier versions of the SDK used. Empty or whitespace-only values count as not set.

### Identify API

Expand Down
Loading